Commit b64270a5 authored by Kushal Pandya's avatar Kushal Pandya

Update Geo admin page to use new design

parent ac13afbc
/* eslint-disable no-new*/
import axios from 'axios';
import SmartInterval from '~/smart_interval';
import { s__ } from '~/locale';
import { parseSeconds, stringifyTime } from './lib/utils/pretty_time';
import { addDelimiter } from './lib/utils/text_utility';
import { timeIntervalInWords } from './lib/utils/datetime_utility';
import timeago from './vue_shared/mixins/timeago';
const healthyClass = 'geo-node-healthy';
const unhealthyClass = 'geo-node-unhealthy';
......@@ -20,18 +22,16 @@ class GeoNodeStatus {
this.$dbReplicationLag = $('.js-db-replication-lag', this.$status);
this.$healthStatus = $('.js-health-status', this.$el);
this.$status = $('.js-geo-node-status', this.$el);
this.$repositoriesSynced = $('.js-repositories-synced', this.$status);
this.$repositoriesFailed = $('.js-repositories-failed', this.$status);
this.$lfsObjectsSynced = $('.js-lfs-objects-synced', this.$status);
this.$lfsObjectsFailed = $('.js-lfs-objects-failed', this.$status);
this.$attachmentsSynced = $('.js-attachments-synced', this.$status);
this.$attachmentsFailed = $('.js-attachments-failed', this.$status);
this.$repositories = $('.js-repositories', this.$status);
this.$lfsObjects = $('.js-lfs-objects', this.$status);
this.$attachments = $('.js-attachments', this.$status);
this.$syncSettings = $('.js-sync-settings', this.$status);
this.$lastEventSeen = $('.js-last-event-seen', this.$status);
this.$lastCursorEvent = $('.js-last-cursor-event', this.$status);
this.$health = $('.js-health', this.$status);
this.$health = $('.js-health-message', this.$status.parent());
this.endpoint = this.$el.data('status-url');
this.$advancedStatus = $('.js-advanced-geo-node-status-toggler', this.$status);
this.$advancedStatus.on('click', GeoNodeStatus.toggleShowAdvancedStatus);
this.$advancedStatus = $('.js-advanced-geo-node-status-toggler', this.$status.parent());
this.$advancedStatus.on('click', GeoNodeStatus.toggleShowAdvancedStatus.bind(this));
this.statusInterval = new SmartInterval({
callback: this.getStatus.bind(this),
......@@ -45,26 +45,123 @@ class GeoNodeStatus {
static toggleShowAdvancedStatus(e) {
const $element = $(e.currentTarget);
const $closestStatus = $element.siblings('.advanced-status');
const $advancedStatusItems = this.$status.find('.js-advanced-status');
$element.find('.fa').toggleClass('fa-angle-down').toggleClass('fa-angle-up');
$closestStatus.toggleClass('hidden');
$element.find('.js-advance-toggle')
.html(gl.utils.spriteIcon($advancedStatusItems.is(':hidden') ? 'angle-up' : 'angle-down', 's16'));
$advancedStatusItems.toggleClass('hidden');
}
static formatCountAndPercentage(count, total, percentage) {
if (count !== null || total != null) {
return `${addDelimiter(count)}/${addDelimiter(total)} (${percentage})`;
static getSyncStatistics({ syncedCount, failedCount, totalCount }) {
const syncedPercent = Math.ceil((syncedCount / totalCount) * 100);
const failedPercent = Math.ceil((failedCount / totalCount) * 100);
const waitingPercent = 100 - syncedPercent - failedPercent;
return {
syncedPercent,
waitingPercent,
failedPercent,
syncedCount,
failedCount,
waitingCount: totalCount - syncedCount - failedCount,
};
}
static renderSyncGraph($itemEl, syncStats) {
const graphItems = [
{
itemSel: '.js-synced',
itemTooltip: s__('GeoNodeSyncStatus|Synced'),
itemCount: syncStats.syncedCount,
itemPercent: syncStats.syncedPercent,
},
{
itemSel: '.js-waiting',
itemTooltip: s__('GeoNodeSyncStatus|Out of sync'),
itemCount: syncStats.waitingCount,
itemPercent: syncStats.waitingPercent,
},
{
itemSel: '.js-failed',
itemTooltip: s__('GeoNodeSyncStatus|Failed'),
itemCount: syncStats.failedCount,
itemPercent: syncStats.failedPercent,
},
];
$itemEl.find('.js-stats-unavailable')
.toggleClass('hidden',
!!graphItems[0].itemCount ||
!!graphItems[1].itemCount ||
!!graphItems[2].itemCount);
graphItems.forEach((item) => {
$itemEl.find(item.itemSel)
.toggleClass('has-value has-tooltip', !!item.itemCount)
.attr('data-original-title', `${item.itemTooltip}: ${item.itemPercent}%`)
.text(item.itemCount || '')
.css('width', `${item.itemPercent}%`);
});
}
static renderEventStats($eventEl, eventId, eventTimestamp) {
const $eventTimestampEl = $eventEl.find('.js-event-timestamp');
let eventDate = notAvailable;
if (eventTimestamp && eventTimestamp > 0) {
eventDate = gl.utils.formatDate(new Date(eventTimestamp * 1000));
}
return notAvailable;
if (eventId) {
$eventEl.find('.js-event-id').text(eventId);
$eventTimestampEl
.attr('title', eventDate)
.text(`(${timeago.methods.timeFormated(eventDate)})`);
}
}
static formatCount(count) {
if (count !== null) {
return addDelimiter(count);
static renderSyncSettings($syncSettings, namespaces, eventStats) {
const { lastEventId, lastEventTimestamp, cursorEventId, cursorEventTimestamp } = eventStats;
const $syncStatusIcon = $syncSettings.find('.js-sync-status-icon');
const DIFFS = {
FIVE_MINS: 300,
HOUR: 3600,
};
let eventDateTime;
let cursorDateTime;
$syncSettings.find('.js-sync-type')
.text(namespaces.length > 0 ? 'Selective' : 'Full');
if (lastEventTimestamp && lastEventTimestamp > 0) {
eventDateTime = new Date(lastEventTimestamp * 1000);
}
return notAvailable;
if (cursorEventTimestamp && cursorEventTimestamp > 0) {
cursorDateTime = new Date(cursorEventTimestamp * 1000);
}
const timeDiffInSeconds = (cursorDateTime - eventDateTime) / 1000;
if (timeDiffInSeconds <= DIFFS.FIVE_MINS) {
// Lag is under 5 mins
$syncStatusIcon.html(gl.utils.spriteIcon('retry', 's16'));
} else if (timeDiffInSeconds > DIFFS.FIVE_MINS &&
timeDiffInSeconds <= DIFFS.HOUR) {
// Lag is between 5 mins to an hour
$syncStatusIcon.html(gl.utils.spriteIcon('warning', 's16'));
$syncSettings.attr('data-original-title', s__('GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage.'));
} else {
// Lag is over an hour
$syncSettings.find('.js-sync-status').addClass('sync-status-failure');
$syncStatusIcon.html(gl.utils.spriteIcon('status_failed', 's16'));
$syncSettings.attr('data-original-title', s__('GeoNodeSyncStatus|Node is failing or broken.'));
}
const timeAgoStr = timeIntervalInWords(timeDiffInSeconds);
const pendingEvents = lastEventId - cursorEventId;
$syncSettings
.find('.js-sync-status-timestamp')
.text(`${timeAgoStr} (${pendingEvents} events)`);
}
getStatus() {
......@@ -80,7 +177,11 @@ class GeoNodeStatus {
handleStatus(status) {
this.setStatusIcon(status.healthy);
this.setHealthStatus(status.healthy);
this.setHealthStatus({
healthy: status.healthy,
healthStatus: status.health_status,
healthMessage: status.health,
});
// Replication lag can be nil if the secondary isn't actually streaming
if (status.db_replication_lag_seconds !== null && status.db_replication_lag_seconds >= 0) {
......@@ -93,73 +194,62 @@ class GeoNodeStatus {
this.$dbReplicationLag.text('UNKNOWN');
}
const repoText = GeoNodeStatus.formatCountAndPercentage(
status.repositories_synced_count,
status.repositories_count,
status.repositories_synced_in_percentage);
const repoFailedText = GeoNodeStatus.formatCount(status.repositories_failed_count);
const lfsText = GeoNodeStatus.formatCountAndPercentage(
status.lfs_objects_synced_count,
status.lfs_objects_count,
status.lfs_objects_synced_in_percentage);
const lfsFailedText = GeoNodeStatus.formatCount(status.lfs_objects_failed_count);
const attachmentText = GeoNodeStatus.formatCountAndPercentage(
status.attachments_synced_count,
status.attachments_count,
status.attachments_synced_in_percentage);
const attachmentFailedText = GeoNodeStatus.formatCount(status.attachments_failed_count);
this.$repositoriesSynced.text(repoText);
this.$repositoriesFailed.text(repoFailedText);
this.$lfsObjectsSynced.text(lfsText);
this.$lfsObjectsFailed.text(lfsFailedText);
this.$attachmentsSynced.text(attachmentText);
this.$attachmentsFailed.text(attachmentFailedText);
let eventDate = notAvailable;
let cursorDate = notAvailable;
let lastEventSeen = notAvailable;
let lastCursorEvent = notAvailable;
if (status.last_event_timestamp !== null && status.last_event_timestamp > 0) {
eventDate = gl.utils.formatDate(new Date(status.last_event_timestamp * 1000));
if (status.repositories_count > 0) {
const repositoriesStats = GeoNodeStatus.getSyncStatistics({
syncedCount: status.repositories_synced_count,
failedCount: status.repositories_failed_count,
totalCount: status.repositories_count,
});
GeoNodeStatus.renderSyncGraph(this.$repositories, repositoriesStats);
}
if (status.cursor_last_event_timestamp !== null && status.cursor_last_event_timestamp > 0) {
cursorDate = gl.utils.formatDate(new Date(status.cursor_last_event_timestamp * 1000));
if (status.lfs_objects_count > 0) {
const lfsObjectsStats = GeoNodeStatus.getSyncStatistics({
syncedCount: status.lfs_objects_synced_count,
failedCount: status.lfs_objects_failed_count,
totalCount: status.lfs_objects_count,
});
GeoNodeStatus.renderSyncGraph(this.$lfsObjects, lfsObjectsStats);
}
if (status.last_event_id !== null) {
lastEventSeen = `${status.last_event_id} (${eventDate})`;
if (status.attachments_count > 0) {
const attachmentsStats = GeoNodeStatus.getSyncStatistics({
syncedCount: status.attachments_synced_count,
failedCount: status.attachments_failed_count,
totalCount: status.attachments_count,
});
GeoNodeStatus.renderSyncGraph(this.$attachments, attachmentsStats);
}
if (status.cursor_last_event_id !== null) {
lastCursorEvent = `${status.cursor_last_event_id} (${cursorDate})`;
if (status.namespaces) {
GeoNodeStatus.renderSyncSettings(
this.$syncSettings,
status.namespaces, {
lastEventId: status.last_event_id,
lastEventTimestamp: status.last_event_timestamp,
cursorEventId: status.cursor_last_event_id,
cursorEventTimestamp: status.cursor_last_event_timestamp,
});
}
this.$lastEventSeen.text(lastEventSeen);
this.$lastCursorEvent.text(lastCursorEvent);
GeoNodeStatus.renderEventStats(
this.$lastEventSeen,
status.last_event_id,
status.last_event_timestamp);
GeoNodeStatus.renderEventStats(
this.$lastCursorEvent,
status.cursor_last_event_id,
status.cursor_last_event_timestamp);
if (status.health === 'Healthy') {
this.$health.text('');
} else {
const strippedData = $('<div>').html(`${status.health}`).text();
this.$health.html(`<code class="geo-health">${strippedData}</code>`);
}
this.$status.show();
this.$status.removeClass('hidden');
}
handleError(err) {
this.setStatusIcon(false);
this.setHealthStatus(false);
this.$health.html(`<code class="geo-health">${err}</code>`);
this.$status.show();
this.$health.text(err);
this.$health.removeClass('hidden');
this.$status.removeClass('hidden');
}
setStatusIcon(healthy) {
......@@ -177,15 +267,20 @@ class GeoNodeStatus {
}
}
setHealthStatus(healthy) {
setHealthStatus({ healthy, healthStatus, healthMessage }) {
if (healthy) {
this.$healthStatus.removeClass(unhealthyClass)
.addClass(healthyClass)
.text('Healthy');
.text(healthMessage);
this.$health.text('');
this.$health.addClass('hidden');
} else {
this.$healthStatus.removeClass(healthyClass)
.addClass(unhealthyClass)
.text('Unhealthy');
.text(healthStatus);
const strippedData = $('<div>').html(`${healthMessage}`).text();
this.$health.text(strippedData);
this.$health.removeClass('hidden');
}
}
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment