Files
crank/apps/ui/js/api.js
T

542 lines
21 KiB
JavaScript

(function() {
var API_BASE = '/api/admin';
var AUTH_BASE = '/api/auth';
var operationEtags = Object.create(null);
var agentEtags = Object.create(null);
function headers(extra) {
return Object.assign({
'Accept': 'application/json',
}, extra || {});
}
function csrfExempt(path) {
return /\/api\/auth\/(?:login|bootstrap\/complete|session\/csrf)$/.test(path);
}
function attachCsrf(path, method, requestOptions) {
if (method === 'GET' || method === 'HEAD' || method === 'OPTIONS' || csrfExempt(path)) {
return;
}
if (!(path.indexOf('/api/auth/') === 0 || path.indexOf('/api/admin/') === 0)) {
return;
}
var token = window.CrankAuth && typeof window.CrankAuth.getCsrfToken === 'function'
? window.CrankAuth.getCsrfToken()
: '';
if (token) {
requestOptions.headers = headers(requestOptions.headers);
requestOptions.headers['x-csrf-token'] = token;
}
}
function attachCorrelation(error, response) {
var requestId = response.headers.get('x-request-id');
var traceId = response.headers.get('x-trace-id');
if (requestId && requestId.length <= 128 && /^[!-~]+$/.test(requestId) && requestId.indexOf(',') === -1 && requestId.indexOf(';') === -1) {
error.requestId = requestId;
}
if (traceId && /^[0-9a-f]{32}$/.test(traceId) && traceId !== '00000000000000000000000000000000') {
error.traceId = traceId;
}
return error;
}
function operationMutationResource(path, method) {
if (!method || method === 'GET') {
return null;
}
var match = path.match(/^(\/api\/admin\/workspaces\/[^/]+\/operations\/[^/?]+)(?:\/(publish|archive|versions))?(?:\?.*)?$/);
if (!match) {
return null;
}
if (method === 'PATCH' || method === 'DELETE' || (method === 'POST' && match[2])) {
return match[1];
}
return null;
}
function operationDetailResource(path, method) {
if (method && method !== 'GET') {
return null;
}
var match = path.match(/^(\/api\/admin\/workspaces\/[^/]+\/operations\/[^/?]+)$/);
return match ? match[1] : null;
}
function agentMutationResource(path, method) {
if (!method || method === 'GET') {
return null;
}
var match = path.match(/^(\/api\/admin\/workspaces\/[^/]+\/agents\/[^/?]+)(?:\/(bindings|publish|unpublish|archive))?(?:\?.*)?$/);
if (!match) {
return null;
}
if (method === 'PATCH' || method === 'DELETE' || (method === 'POST' && match[2])) {
return match[1];
}
return null;
}
function agentDetailResource(path, method) {
if (method && method !== 'GET') {
return null;
}
var match = path.match(/^(\/api\/admin\/workspaces\/[^/]+\/agents\/[^/?]+)$/);
return match ? match[1] : null;
}
async function request(path, options) {
var requestOptions = Object.assign({
credentials: 'same-origin',
headers: headers(),
}, options || {});
var method = (requestOptions.method || 'GET').toUpperCase();
attachCsrf(path, method, requestOptions);
var mutationResource = operationMutationResource(path, method);
var agentMutation = agentMutationResource(path, method);
if (mutationResource) {
if (!operationEtags[mutationResource]) {
await request(mutationResource);
}
requestOptions.headers = headers(requestOptions.headers);
requestOptions.headers['If-Match'] = operationEtags[mutationResource];
}
if (agentMutation) {
if (!agentEtags[agentMutation]) {
await request(agentMutation);
}
requestOptions.headers = headers(requestOptions.headers);
requestOptions.headers['If-Match'] = agentEtags[agentMutation];
}
var response = await fetch(path, requestOptions);
var detailResource = operationDetailResource(path, method);
var agentDetail = agentDetailResource(path, method);
var responseEtag = response.headers.get('etag');
if (detailResource && responseEtag) {
operationEtags[detailResource] = responseEtag;
}
if (agentDetail && responseEtag) {
agentEtags[agentDetail] = responseEtag;
}
if (response.status === 204) {
if (mutationResource) {
delete operationEtags[mutationResource];
}
return null;
}
var payload = null;
var text = await response.text();
if (text) {
try {
payload = JSON.parse(text);
} catch (_error) {}
}
if (!response.ok) {
if (mutationResource && (response.status === 409 || response.status === 428)) {
delete operationEtags[mutationResource];
}
if (agentMutation && (response.status === 409 || response.status === 428)) {
delete agentEtags[agentMutation];
}
if (response.status === 401 && window.CrankAuth && typeof window.CrankAuth.handleUnauthorized === 'function') {
window.CrankAuth.handleUnauthorized();
}
var looksLikeHtml = /^\s*</.test(text || '');
var message = payload && payload.error
? payload.error.message
: payload && payload.message
? payload.message
: text && !looksLikeHtml
? text
: ('HTTP ' + response.status);
var error = new Error(message);
error.status = response.status;
error.payload = payload;
var errorCode = payload && payload.error && typeof payload.error === 'object'
? (payload.error.code || payload.error.error_code)
: payload && (payload.code || payload.error_code);
if (typeof errorCode === 'string' && /^[A-Za-z0-9._-]{1,128}$/.test(errorCode)) {
error.code = errorCode;
}
throw attachCorrelation(error, response);
}
if (mutationResource) {
delete operationEtags[mutationResource];
}
if (agentMutation) {
delete agentEtags[agentMutation];
}
if (text && payload === null) {
throw attachCorrelation(new Error('Backend returned a non-JSON response'), response);
}
return payload;
}
async function requestText(path, options) {
var response = await fetch(path, Object.assign({
credentials: 'same-origin',
headers: headers(),
}, options || {}));
var text = await response.text();
var payload = null;
if (text) {
try {
payload = JSON.parse(text);
} catch (_error) {}
}
if (!response.ok) {
if (response.status === 401 && window.CrankAuth && typeof window.CrankAuth.handleUnauthorized === 'function') {
window.CrankAuth.handleUnauthorized();
}
var message = payload && payload.error
? payload.error.message
: payload && payload.message
? payload.message
: text
? text
: ('HTTP ' + response.status);
var error = new Error(message);
error.status = response.status;
error.payload = payload;
throw attachCorrelation(error, response);
}
return text;
}
function get(path) {
return request(API_BASE + path);
}
function post(path, body) {
return request(API_BASE + path, {
method: 'POST',
headers: headers({ 'Content-Type': 'application/json' }),
body: JSON.stringify(body),
});
}
function patch(path, body) {
return request(API_BASE + path, {
method: 'PATCH',
headers: headers({ 'Content-Type': 'application/json' }),
body: JSON.stringify(body),
});
}
function del(path) {
return request(API_BASE + path, { method: 'DELETE' });
}
function query(params) {
var search = new URLSearchParams();
Object.keys(params || {}).forEach(function(key) {
var value = params[key];
if (value === undefined || value === null || value === '') {
return;
}
search.set(key, value);
});
var encoded = search.toString();
return encoded ? ('?' + encoded) : '';
}
window.CrankApi = {
getBootstrapStatus: function() {
return request(AUTH_BASE + '/bootstrap/status');
},
completeBootstrap: function(payload) {
return request(AUTH_BASE + '/bootstrap/complete', {
method: 'POST',
headers: headers({ 'Content-Type': 'application/json' }),
body: JSON.stringify(payload),
});
},
login: function(payload) {
return request(AUTH_BASE + '/login', {
method: 'POST',
headers: headers({ 'Content-Type': 'application/json' }),
body: JSON.stringify(payload),
});
},
logout: function() {
return request(AUTH_BASE + '/logout', {
method: 'POST',
headers: headers({ 'Content-Type': 'application/json' }),
body: JSON.stringify({}),
});
},
getSession: function() {
return request(AUTH_BASE + '/session');
},
refreshSessionCsrf: function() {
return request(AUTH_BASE + '/session/csrf', {
method: 'POST',
headers: headers({ 'Content-Type': 'application/json' }),
body: JSON.stringify({}),
});
},
getProfile: function() {
return request(AUTH_BASE + '/profile');
},
updateProfile: function(payload) {
return request(AUTH_BASE + '/profile', {
method: 'PATCH',
headers: headers({ 'Content-Type': 'application/json' }),
body: JSON.stringify(payload),
});
},
changePassword: function(payload) {
return request(AUTH_BASE + '/password', {
method: 'POST',
headers: headers({ 'Content-Type': 'application/json' }),
body: JSON.stringify(payload),
});
},
listWorkspaces: function() {
return get('/workspaces');
},
getCapabilities: function() {
return get('/capabilities');
},
getWorkspace: function(workspaceId) {
return get('/workspaces/' + encodeURIComponent(workspaceId));
},
getOnboarding: function(workspaceId) {
return get('/workspaces/' + encodeURIComponent(workspaceId) + '/onboarding');
},
recordOnboardingEvent: function(workspaceId, payload, options) {
return request(API_BASE + '/workspaces/' + encodeURIComponent(workspaceId) + '/onboarding/events', Object.assign({}, options || {}, {
method: 'POST',
headers: headers({ 'Content-Type': 'application/json' }),
body: JSON.stringify(payload),
}));
},
resetOnboardingSelection: function(workspaceId, expectedRevision) {
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/onboarding/reset-selection', {
expected_revision: expectedRevision,
});
},
updateWorkspace: function(workspaceId, payload) {
return patch('/workspaces/' + encodeURIComponent(workspaceId), payload);
},
exportWorkspace: function(workspaceId) {
return get('/workspaces/' + encodeURIComponent(workspaceId) + '/export');
},
listOperations: function(workspaceId) {
return get('/workspaces/' + encodeURIComponent(workspaceId) + '/operations');
},
getOperation: function(workspaceId, operationId) {
return get('/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId));
},
getOperationVersion: function(workspaceId, operationId, version) {
return get('/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId) + '/versions/' + encodeURIComponent(version));
},
createOperation: function(workspaceId, payload) {
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/operations', payload);
},
analyzeOperationQuality: function(workspaceId, payload) {
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/operations/analyze-quality', payload);
},
updateOperation: function(workspaceId, operationId, payload) {
return patch('/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId), payload);
},
deleteOperation: function(workspaceId, operationId) {
return del('/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId));
},
archiveOperation: function(workspaceId, operationId) {
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId) + '/archive', {});
},
publishOperation: function(workspaceId, operationId, version) {
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId) + '/publish', {
version: version,
});
},
createOperationVersion: function(workspaceId, operationId, payload) {
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId) + '/versions', payload);
},
runOperationTest: function(workspaceId, operationId, payload) {
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId) + '/test-runs', payload);
},
uploadInputSample: function(workspaceId, operationId, sample) {
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId) + '/samples/input-json', sample);
},
uploadOutputSample: function(workspaceId, operationId, sample) {
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId) + '/samples/output-json', sample);
},
generateDraft: function(workspaceId, operationId, payload) {
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId) + '/drafts/generate', payload || {});
},
exportOperation: function(workspaceId, operationId, params) {
return requestText(
API_BASE + '/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId) + '/export' + query(params),
{
headers: headers({ 'Accept': 'application/yaml' }),
}
);
},
importOperation: async function(workspaceId, yamlDocument, mode, existingOperationId) {
var importHeaders = headers({ 'Content-Type': 'application/yaml' });
if (existingOperationId) {
var resource = API_BASE + '/workspaces/' + encodeURIComponent(workspaceId)
+ '/operations/' + encodeURIComponent(existingOperationId);
if (!operationEtags[resource]) {
await request(resource);
}
importHeaders['If-Match'] = operationEtags[resource];
}
var result = await request(
API_BASE + '/workspaces/' + encodeURIComponent(workspaceId) + '/operations/import' + query({ mode: mode }),
{
method: 'POST',
headers: importHeaders,
body: yamlDocument,
}
);
if (existingOperationId) {
delete operationEtags[resource];
}
return result;
},
previewOpenApiImport: function(workspaceId, documentText) {
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/imports/openapi/preview', {
document: documentText,
});
},
createOpenApiImport: function(workspaceId, jobId, payload) {
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/imports/openapi/' + encodeURIComponent(jobId) + '/create', payload);
},
listAgents: function(workspaceId) {
return get('/workspaces/' + encodeURIComponent(workspaceId) + '/agents');
},
createAgent: function(workspaceId, payload) {
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/agents', payload);
},
getAgent: function(workspaceId, agentId) {
return get('/workspaces/' + encodeURIComponent(workspaceId) + '/agents/' + encodeURIComponent(agentId));
},
updateAgent: function(workspaceId, agentId, payload) {
return patch('/workspaces/' + encodeURIComponent(workspaceId) + '/agents/' + encodeURIComponent(agentId), payload);
},
deleteAgent: function(workspaceId, agentId) {
return del('/workspaces/' + encodeURIComponent(workspaceId) + '/agents/' + encodeURIComponent(agentId));
},
saveAgentBindings: function(workspaceId, agentId, payload) {
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/agents/' + encodeURIComponent(agentId) + '/bindings', payload);
},
previewAgentToolSearch: function(workspaceId, payload) {
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/agents/tool-search/preview', payload);
},
publishAgent: function(workspaceId, agentId, payload) {
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/agents/' + encodeURIComponent(agentId) + '/publish', payload);
},
unpublishAgent: function(workspaceId, agentId) {
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/agents/' + encodeURIComponent(agentId) + '/unpublish', {});
},
archiveAgent: function(workspaceId, agentId) {
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/agents/' + encodeURIComponent(agentId) + '/archive', {});
},
listAgentPlatformApiKeys: function(workspaceId, agentId) {
return get('/workspaces/' + encodeURIComponent(workspaceId) + '/agents/' + encodeURIComponent(agentId) + '/platform-api-keys');
},
createAgentPlatformApiKey: function(workspaceId, agentId, payload) {
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/agents/' + encodeURIComponent(agentId) + '/platform-api-keys', payload);
},
revokeAgentPlatformApiKey: function(workspaceId, agentId, keyId) {
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/agents/' + encodeURIComponent(agentId) + '/platform-api-keys/' + encodeURIComponent(keyId) + '/revoke', {});
},
deleteAgentPlatformApiKey: function(workspaceId, agentId, keyId) {
return del('/workspaces/' + encodeURIComponent(workspaceId) + '/agents/' + encodeURIComponent(agentId) + '/platform-api-keys/' + encodeURIComponent(keyId));
},
listSecrets: function(workspaceId) {
return get('/workspaces/' + encodeURIComponent(workspaceId) + '/secrets');
},
createSecret: function(workspaceId, payload) {
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/secrets', payload);
},
getSecret: function(workspaceId, secretId) {
return get('/workspaces/' + encodeURIComponent(workspaceId) + '/secrets/' + encodeURIComponent(secretId));
},
rotateSecret: function(workspaceId, secretId, payload) {
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/secrets/' + encodeURIComponent(secretId) + '/rotate', payload);
},
deleteSecret: function(workspaceId, secretId) {
return del('/workspaces/' + encodeURIComponent(workspaceId) + '/secrets/' + encodeURIComponent(secretId));
},
listAuthProfiles: function(workspaceId) {
return get('/workspaces/' + encodeURIComponent(workspaceId) + '/auth-profiles');
},
createAuthProfile: function(workspaceId, payload) {
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/auth-profiles', payload);
},
listUpstreams: function(workspaceId) {
return get('/workspaces/' + encodeURIComponent(workspaceId) + '/upstreams');
},
createUpstream: function(workspaceId, payload) {
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/upstreams', payload);
},
updateUpstream: function(workspaceId, upstreamId, payload) {
return patch('/workspaces/' + encodeURIComponent(workspaceId) + '/upstreams/' + encodeURIComponent(upstreamId), payload);
},
listLogs: function(workspaceId, params) {
return get('/workspaces/' + encodeURIComponent(workspaceId) + '/logs' + query(params));
},
exportLogsCsv: function(workspaceId, params) {
return requestText(
API_BASE + '/workspaces/' + encodeURIComponent(workspaceId) + '/logs/export.csv' + query(params)
);
},
exportUsageCsv: function(workspaceId, params) {
return requestText(
API_BASE + '/workspaces/' + encodeURIComponent(workspaceId) + '/usage/export.csv' + query(params)
);
},
getLog: function(workspaceId, logId) {
return get('/workspaces/' + encodeURIComponent(workspaceId) + '/logs/' + encodeURIComponent(logId));
},
listApprovals: function(workspaceId, params) {
return get('/workspaces/' + encodeURIComponent(workspaceId) + '/approvals' + query(params));
},
getApproval: function(workspaceId, approvalId) {
return get('/workspaces/' + encodeURIComponent(workspaceId) + '/approvals/' + encodeURIComponent(approvalId));
},
approveApproval: function(workspaceId, approvalId, payload) {
return post(
'/workspaces/' + encodeURIComponent(workspaceId) + '/approvals/' + encodeURIComponent(approvalId) + '/approve',
payload || { approve: 'yes' }
);
},
denyApproval: function(workspaceId, approvalId, payload) {
return post(
'/workspaces/' + encodeURIComponent(workspaceId) + '/approvals/' + encodeURIComponent(approvalId) + '/deny',
payload || { approve: 'no' }
);
},
getUsageOverview: function(workspaceId, params) {
return get('/workspaces/' + encodeURIComponent(workspaceId) + '/usage' + query(params));
},
getOperationUsage: function(workspaceId, operationId, params) {
return get(
'/workspaces/' + encodeURIComponent(workspaceId) + '/usage/operations/' + encodeURIComponent(operationId) + query(params)
);
},
getAgentUsage: function(workspaceId, agentId, params) {
return get(
'/workspaces/' + encodeURIComponent(workspaceId) + '/usage/agents/' + encodeURIComponent(agentId) + query(params)
);
},
};
}());