Cluster management/newdashboard task (#6060)

* initial commit: added cluster status notebook and dashboard task

* following the previous naming conventions

* endpoint widget changes to accomodatw naming changes

* management-proxy/mgmtproxy chnages

* updates to address the comments and added the new copy image with hover text.

* added user select for making the table selectable

* localize changes

* added the final documented notebook

* reset execution_count to 0 for all cells

* style changes

* updated the url to point to private repo
This commit is contained in:
Maddy
2019-06-25 22:46:11 -07:00
committed by GitHub
parent 6142109bf5
commit 32235b0cb6
13 changed files with 310 additions and 21 deletions

View File

@@ -64,6 +64,7 @@ export enum MssqlClusterItemsSubType {
// SPARK JOB SUBMISSION //////////////////////////////////////////////////////////
export const mssqlClusterNewNotebookTask = 'mssqlCluster.task.newNotebook';
export const mssqlClusterOpenNotebookTask = 'mssqlCluster.task.openNotebook';
export const mssqlopenClusterStatusNotebook = 'mssqlCluster.task.openClusterStatusNotebook';
export const mssqlClusterLivySubmitSparkJobCommand = 'mssqlCluster.livy.cmd.submitSparkJob';
export const mssqlClusterLivySubmitSparkJobFromFileCommand = 'mssqlCluster.livy.cmd.submitFileToSparkJob';
export const mssqlClusterLivySubmitSparkJobTask = 'mssqlCluster.livy.task.submitSparkJob';

View File

@@ -137,36 +137,51 @@ export async function activate(context: vscode.ExtensionContext): Promise<MssqlE
azdata.ui.registerModelViewProvider('bdc-endpoints', async (view) => {
const endpointsArray: Array<Utils.IEndpoint> = Object.assign([], view.serverInfo.options['clusterEndpoints']);
endpointsArray.forEach(endpointInfo => {
endpointInfo.isHyperlink = true;
endpointInfo.hyperlink = 'https://' + endpointInfo.ipAddress + ':' + endpointInfo.port;
});
if (endpointsArray.length > 0) {
const managementProxyEp = endpointsArray.find(e => e.serviceName === 'management-proxy');
const managementProxyEp = endpointsArray.find(e => e.serviceName === 'management-proxy' || e.serviceName === 'mgmtproxy');
if (managementProxyEp) {
endpointsArray.push(getCustomEndpoint(managementProxyEp, 'Grafana Dashboard', '/grafana'));
endpointsArray.push(getCustomEndpoint(managementProxyEp, 'Kibana Dashboard', '/kibana'));
endpointsArray.push(getCustomEndpoint(managementProxyEp, localize("grafana", "Metrics Dashboard"), '/grafana/d/wZx3OUdmz'));
endpointsArray.push(getCustomEndpoint(managementProxyEp, localize("kibana", "Log Search Dashboard"), '/kibana/app/kibana#/discover'));
}
const gatewayEp = endpointsArray.find(e => e.serviceName === 'gateway');
if (gatewayEp) {
endpointsArray.push(getCustomEndpoint(gatewayEp, 'Spark History', '/gateway/default/sparkhistory'));
endpointsArray.push(getCustomEndpoint(gatewayEp, 'Yarn History', '/gateway/default/yarn'));
endpointsArray.push(getCustomEndpoint(gatewayEp, localize("sparkHostory", "Spark Job Monitoring"), '/gateway/default/sparkhistory'));
endpointsArray.push(getCustomEndpoint(gatewayEp, localize("yarnHistory", "Spark Resource Management"), '/gateway/default/yarn'));
}
const container = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '100%', height: '100%', alignItems: 'left' }).component();
endpointsArray.forEach(endpointInfo => {
const endPointRow = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row' }).component();
const nameCell = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: endpointInfo.serviceName }).component();
endPointRow.addItem(nameCell, { CSSStyles: { 'width': '30%', 'font-weight': '600' } });
const nameCell = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: getFriendlyEndpointNames(endpointInfo.serviceName) }).component();
endPointRow.addItem(nameCell, { CSSStyles: { 'width': '35%', 'font-weight': '600', 'user-select': 'text' } });
if (endpointInfo.isHyperlink) {
const linkCell = view.modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({ label: endpointInfo.hyperlink, url: endpointInfo.hyperlink, position: '' }).component();
endPointRow.addItem(linkCell, { CSSStyles: { 'width': '70%', 'color': 'blue', 'text-decoration': 'underline', 'padding-top': '10px' } });
const linkCell = view.modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({ label: endpointInfo.hyperlink, url: endpointInfo.hyperlink }).component();
endPointRow.addItem(linkCell, { CSSStyles: { 'width': '62%', 'color': '#0078d4', 'text-decoration': 'underline', 'padding-top': '10px' } });
}
else {
const endpointCell = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: endpointInfo.ipAddress + ':' + endpointInfo.port }).component();
endPointRow.addItem(endpointCell, { CSSStyles: { 'width': '70%' } });
endPointRow.addItem(endpointCell, { CSSStyles: { 'width': '62%', 'user-select': 'text' } });
}
container.addItem(endPointRow, { CSSStyles: { 'padding-left': '10px', 'border-top': 'solid 1px #ccc', 'box-sizing': 'border-box' } });
const copyValueCell = view.modelBuilder.button().component();
copyValueCell.iconPath = { light: context.asAbsolutePath('resources/light/copy.png'), dark: context.asAbsolutePath('resources/dark/copy_inverse.png') };
copyValueCell.onDidClick(() => {
vscode.env.clipboard.writeText(endpointInfo.hyperlink);
});
copyValueCell.title = localize("copyText", "Copy");
copyValueCell.iconHeight = '14px';
copyValueCell.iconWidth = '14px';
endPointRow.addItem(copyValueCell, { CSSStyles: { 'width': '3%', 'padding-top': '10px' } });
container.addItem(endPointRow, { CSSStyles: { 'padding-left': '10px', 'border-top': 'solid 1px #ccc', 'box-sizing': 'border-box', 'user-select': 'text' } });
});
const endpointsContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '100%', height: '100%', alignItems: 'left' }).component();
endpointsContainer.addItem(container, { CSSStyles: { 'padding-top': '25px' } });
const endpointsContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '540px', height: '100%', alignItems: 'left' }).component();
endpointsContainer.addItem(container, { CSSStyles: { 'padding-top': '25px', 'padding-left': '5px' } });
await view.initializeModel(endpointsContainer);
}
@@ -187,6 +202,30 @@ export async function activate(context: vscode.ExtensionContext): Promise<MssqlE
return null;
}
function getFriendlyEndpointNames(name: string): string {
let friendlyName: string = name;
switch (name) {
case 'app-proxy':
friendlyName = localize("appproxy", "Application Proxy");
break;
case 'controller':
friendlyName = localize("controller", "Cluster Management Service");
break;
case 'gateway':
friendlyName = localize("gateway", "HDFS and Spark");
break;
case 'management-proxy':
friendlyName = localize("managementproxy", "Management Proxy");
break;
case 'mgmtproxy':
friendlyName = localize("mgmtproxy", "Management Proxy");
break;
default:
break;
}
return friendlyName;
}
let api: MssqlExtensionApi = {
getMssqlObjectExplorerBrowser(): MssqlObjectExplorerBrowser {
return {
@@ -228,6 +267,9 @@ function activateNotebookTask(appContext: AppContext): void {
apiWrapper.registerTaskHandler(Constants.mssqlClusterOpenNotebookTask, (profile: azdata.IConnectionProfile) => {
return handleOpenNotebookTask(profile);
});
apiWrapper.registerTaskHandler(Constants.mssqlopenClusterStatusNotebook, (profile: azdata.IConnectionProfile) => {
return handleOpenClusterStatusNotebookTask(profile, appContext);
});
}
function saveProfileAndCreateNotebook(profile: azdata.IConnectionProfile): Promise<void> {
@@ -297,6 +339,21 @@ async function handleOpenNotebookTask(profile: azdata.IConnectionProfile): Promi
}
}
async function handleOpenClusterStatusNotebookTask(profile: azdata.IConnectionProfile, appContext: AppContext): Promise<void> {
const notebookRelativePath = 'notebooks/tsg/cluster-status.ipynb';
const notebookFullPath = path.join(appContext.extensionContext.extensionPath, notebookRelativePath);
if (!Utils.fileExists(notebookFullPath)) {
vscode.window.showErrorMessage(localize("fileNotFound", "Unable to find the file specified"));
} else {
const targetFile = Utils.getTargetFileName(notebookFullPath);
Utils.copyFile(notebookFullPath, targetFile);
let fileUri = vscode.Uri.file(targetFile);
await azdata.nb.showNotebookDocument(fileUri, {
connectionProfile: profile,
preview: false
});
}
}
function generateServerOptions(executablePath: string): ServerOptions {
let launchArgs = Utils.getCommonLaunchArgsAndCleanupOldLogFiles('sqltools', executablePath);
return { command: executablePath, args: launchArgs, transport: TransportKind.stdio };

View File

@@ -10,6 +10,7 @@ import * as crypto from 'crypto';
import * as os from 'os';
import * as findRemoveSync from 'find-remove';
import * as constants from './constants';
import * as fs from 'fs';
const configTracingLevel = 'tracingLevel';
const configLogRetentionMinutes = 'logRetentionMinutes';
@@ -29,6 +30,34 @@ export function getAppDataPath() {
}
}
/**
* Get a file name that is not already used in the target directory
* @param filePath source notebook file name
* @param fileExtension file type
*/
export function getTargetFileName(filePath: string): string {
const targetDirectory = os.homedir();
const fileExtension = path.extname(filePath);
const baseName = path.basename(filePath, fileExtension);
let targetFileName;
let idx = 0;
do {
const suffix = idx === 0 ? '' : `-${idx}`;
targetFileName = path.join(targetDirectory, `${baseName}${suffix}${fileExtension}`);
idx++;
} while (fs.existsSync(targetFileName));
return targetFileName;
}
export function fileExists(file: string): boolean {
return fs.existsSync(file);
}
export function copyFile(source: string, target: string): void {
fs.copyFileSync(source, target);
}
export function removeOldLogFiles(prefix: string): JSON {
return findRemoveSync(getDefaultLogDir(), { prefix: `${prefix}_`, age: { seconds: getConfigLogRetentionSeconds() }, limit: getConfigLogFilesRemovalLimit() });
}