blob: 75dee5f0ed23c354e04295201906d9090accfc29 [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 '@webcomponents/url';
import Constants from './constants';
import FauxtonAPI from '../../core/api';
import Helpers from '../../helpers';
import {get, post, put} from '../../core/ajax';
import base64 from 'base-64';
import _ from 'lodash';
let newApiPromise = null;
export const supportNewApi = (forceCheck) => {
if (!newApiPromise || forceCheck) {
newApiPromise = new FauxtonAPI.Promise((resolve) => {
const url = Helpers.getServerUrl('/_scheduler/jobs');
get(url, {raw: true})
.then(resp => {
if (resp.status > 202) {
return resolve(false);
}
resolve(true);
});
});
}
return newApiPromise;
};
export const encodeFullUrl = (fullUrl) => {
if (!fullUrl) {return '';}
const url = new URL(fullUrl);
return `${url.origin}/${encodeURIComponent(url.pathname.slice(1))}`;
};
export const decodeFullUrl = (fullUrl) => {
if (!fullUrl) {return '';}
const url = new URL(fullUrl);
return `${url.origin}/${decodeURIComponent(url.pathname.slice(1))}`;
};
export const getUsername = () => {
return FauxtonAPI.session.user().name;
};
export const getAuthHeaders = (username, password) => {
if (!username || !password) {
return {};
}
return {
'Authorization': 'Basic ' + base64.encode(username + ':' + password)
};
};
export const getCredentialsFromUrl = (url) => {
const index = url.lastIndexOf('@');
if (index === -1) {
return {
username: '',
password: ''
};
}
const startIndex = url.startsWith("https") ? 8 : 7;
const rawCreds = url.slice(startIndex, index);
const colonIndex = rawCreds.indexOf(':');
const username = rawCreds.slice(0, colonIndex);
const password = rawCreds.slice(colonIndex + 1, rawCreds.length);
return {
username,
password
};
};
export const removeCredentialsFromUrl = (url) => {
const index = url.lastIndexOf('@');
if (index === -1) {
return url;
}
const protocol = url.startsWith("https") ? "https://" : 'http://';
const cleanUrl = url.slice(index + 1);
return protocol + cleanUrl;
};
export const getSource = ({
replicationSource,
localSource,
remoteSource,
sourceAuthType,
sourceAuth
},
{origin, pathname} = window.location) => {
const source = {};
if (replicationSource === Constants.REPLICATION_SOURCE.LOCAL) {
const encodedLocalTarget = encodeURIComponent(localSource);
const root = Helpers.getRootUrl({origin, pathname});
source.url = `${root}${encodedLocalTarget}`;
} else {
source.url = encodeFullUrl(removeCredentialsFromUrl(remoteSource));
}
setCredentials(source, sourceAuthType, sourceAuth);
return source;
};
export const getTarget = ({
replicationTarget,
localTarget,
remoteTarget,
targetAuthType,
targetAuth
},
//this allows us to mock out window.location for our tests
{origin, pathname} = window.location) => {
const target = {};
if (replicationTarget === Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE ||
replicationTarget === Constants.REPLICATION_TARGET.EXISTING_REMOTE_DATABASE) {
target.url = encodeFullUrl(removeCredentialsFromUrl(remoteTarget));
} else {
const encodedLocalTarget = encodeURIComponent(localTarget);
const root = Helpers.getRootUrl({origin, pathname});
target.url = `${root}${encodedLocalTarget}`;
}
setCredentials(target, targetAuthType, targetAuth);
return target;
};
const setCredentials = (target, authType, auth) => {
if (!authType || authType === Constants.REPLICATION_AUTH_METHOD.NO_AUTH) {
target.headers = {};
} else if (authType === Constants.REPLICATION_AUTH_METHOD.BASIC) {
target.headers = getAuthHeaders(auth.username, auth.password);
} else {
// Tries to set creds using one of the custom auth methods
// The extension should provide:
// - 'setCredentials(target, auth)' method which sets the 'auth' credentials into 'target' which is the 'target'/'source' field of the replication doc.
const authExtensions = FauxtonAPI.getExtensions('Replication:Auth');
if (authExtensions) {
authExtensions.filter(ext => ext.typeValue === authType).map(ext => {
if (ext.setCredentials) {
ext.setCredentials(target, auth);
}
});
}
}
};
export const createTarget = (replicationTarget) => {
if (_.includes([
Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE,
Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE],
replicationTarget)) {
return true;
}
return false;
};
export const continuous = (replicationType) => {
if (replicationType === Constants.REPLICATION_TYPE.CONTINUOUS) {
return true;
}
return false;
};
export const addDocIdAndRev = (docId, _rev, doc) => {
if (docId) {
doc._id = docId;
}
if (_rev) {
doc._rev = _rev;
}
return doc;
};
export const createReplicationDoc = ({
replicationTarget,
replicationSource,
replicationType,
replicationDocName,
localTarget,
localSource,
remoteTarget,
remoteSource,
_rev,
sourceAuthType,
sourceAuth,
targetAuthType,
targetAuth,
targetDatabasePartitioned
}) => {
const username = getUsername();
const replicationDoc = {
user_ctx: {
name: username,
roles: ['_admin', '_reader', '_writer']
},
source: getSource({
replicationSource,
localSource,
remoteSource,
sourceAuthType,
sourceAuth
}),
target: getTarget({
replicationTarget,
replicationSource,
remoteTarget,
localTarget,
targetAuthType,
targetAuth
}),
create_target: createTarget(replicationTarget),
continuous: continuous(replicationType),
};
if (targetDatabasePartitioned) {
replicationDoc.create_target_params = {
partitioned: true
};
}
return addDocIdAndRev(replicationDocName, _rev, replicationDoc);
};
export const removeSensitiveUrlInfo = (url) => {
try {
const urlObj = new URL(url);
return `${urlObj.origin}/${decodeURIComponent(urlObj.pathname.slice(1))}`;
} catch (e) {
return url;
}
};
export const getDocUrl = (doc) => {
let url = doc;
if (!doc) {
return '';
}
if (typeof doc === "object") {
url = doc.url;
}
return removeSensitiveUrlInfo(url);
};
export const parseReplicationDocs = (rows) => {
return rows.map(row => row.doc).map(doc => {
return {
_id: doc._id,
_rev: doc._rev,
selected: false, //use this field for bulk delete in the ui
source: getDocUrl(doc.source),
target: getDocUrl(doc.target),
createTarget: doc.create_target,
continuous: doc.continuous === true ? true : false,
status: doc._replication_state,
errorMsg: doc._replication_state_reason ? doc._replication_state_reason : '',
statusTime: new Date(doc._replication_state_time),
startTime: new Date(doc._replication_start_time),
url: `#/database/_replicator/${encodeURIComponent(doc._id)}`,
raw: doc
};
});
};
export const convertState = (state) => {
if (state.toLowerCase() === 'error' || state.toLowerCase() === 'crashing') {
return 'retrying';
}
return state;
};
export const combineDocsAndScheduler = (docs, schedulerDocs) => {
return docs.map(doc => {
const schedule = schedulerDocs.find(s => s.doc_id === doc._id);
if (!schedule) {
return doc;
}
doc.status = convertState(schedule.state);
if (schedule.start_time) {
doc.startTime = new Date(schedule.start_time);
}
if (schedule.last_updated) {
doc.stateTime = new Date(schedule.last_updated);
}
return doc;
});
};
export const fetchReplicationDocs = () => {
return supportNewApi()
.then(newApi => {
const url = Helpers.getServerUrl('/_replicator/_all_docs?include_docs=true&limit=100');
const docsPromise = get(url)
.then((res) => {
if (res.error) {
return [];
}
return parseReplicationDocs(res.rows.filter(row => row.id.indexOf("_design/") === -1));
});
if (!newApi) {
return docsPromise;
}
const schedulerPromise = fetchSchedulerDocs();
return FauxtonAPI.Promise.join(docsPromise, schedulerPromise, (docs, schedulerDocs) => {
return combineDocsAndScheduler(docs, schedulerDocs);
})
.catch(() => {
return [];
});
});
};
export const fetchSchedulerDocs = () => {
const url = Helpers.getServerUrl('/_scheduler/docs?include_docs=true');
return get(url)
.then((res) => {
if (res.error) {
return [];
}
return res.docs;
});
};
export const checkReplicationDocID = (docId) => {
return new Promise((resolve) => {
const url = Helpers.getServerUrl(`/_replicator/${docId}`);
get(url)
.then(resp => {
if (resp.error === "not_found") {
resolve(false);
return;
}
resolve(true);
});
});
};
export const parseReplicateInfo = (resp) => {
return resp.jobs.filter(job => job.database === null).map(job => {
return {
_id: job.id,
source: getDocUrl(job.source.slice(0, job.source.length - 1)),
target: getDocUrl(job.target.slice(0, job.target.length - 1)),
startTime: new Date(job.start_time),
statusTime: new Date(job.last_updated),
//making an asumption here that the first element is the latest
status: convertState(job.history[0].type),
errorMsg: '',
selected: false,
continuous: /continuous/.test(job.id),
raw: job
};
});
};
export const fetchReplicateInfo = () => {
return supportNewApi()
.then(newApi => {
if (!newApi) {
return [];
}
const url = Helpers.getServerUrl('/_scheduler/jobs');
return get(url)
.then(resp => {
return parseReplicateInfo(resp);
});
});
};
export const deleteReplicatesApi = (replicates) => {
const promises = replicates.map(replicate => {
const data = {
replication_id: replicate._id,
cancel: true
};
const url = Helpers.getServerUrl('/_replicate');
return post(url, data);
});
return FauxtonAPI.Promise.all(promises);
};
export const createReplicatorDB = () => {
const url = Helpers.getServerUrl('/_replicator');
return put(url)
.then(res => {
if (!res.ok) {
throw {reason: 'Failed to create the _replicator database.'};
}
return true;
});
};