diff --git a/src/webportal/src/app/vc/vc-modal-component.ejs b/src/webportal/src/app/vc/vc-modal-component.ejs new file mode 100644 index 0000000000..c6430d2b61 --- /dev/null +++ b/src/webportal/src/app/vc/vc-modal-component.ejs @@ -0,0 +1,61 @@ + + + + + diff --git a/src/webportal/src/app/vc/vc.component.ejs b/src/webportal/src/app/vc/vc.component.ejs index 2c231c70c0..2773b52d2f 100644 --- a/src/webportal/src/app/vc/vc.component.ejs +++ b/src/webportal/src/app/vc/vc.component.ejs @@ -5,6 +5,9 @@
+ <% if (isAdmin === 'true') { %> + + <% } %> @@ -15,6 +18,7 @@ + @@ -43,10 +47,19 @@ + <% } %> @@ -54,4 +67,5 @@
CPUs GPUs Active JobsStatus Actions
<%= data[vcName]['numJobs'] %> + <%= convertState(vcName, data[vcName]['status']) %> + View Jobs   |   Go to Yarn Page + <% if (isAdmin === 'true') { %> +   |   + Delete +   |   + Edit + <% } %>
-
\ No newline at end of file + <%= modal({'vcDefault': data.default}) %> + diff --git a/src/webportal/src/app/vc/vc.component.js b/src/webportal/src/app/vc/vc.component.js index 553bea63bf..9b6674f8d7 100644 --- a/src/webportal/src/app/vc/vc.component.js +++ b/src/webportal/src/app/vc/vc.component.js @@ -17,6 +17,7 @@ // +require('bootstrap/js/modal.js'); require('datatables.net/js/jquery.dataTables.js'); require('datatables.net-bs/js/dataTables.bootstrap.js'); require('datatables.net-bs/css/dataTables.bootstrap.css'); @@ -27,10 +28,13 @@ const url = require('url'); require('./vc.component.scss'); const vcComponent = require('./vc.component.ejs'); const breadcrumbComponent = require('../job/breadcrumb/breadcrumb.component.ejs'); +const vcModelComponent = require('./vc-modal-component.ejs'); const webportalConfig = require('../config/webportal.config.js'); +const userAuth = require('../user/user-auth/user-auth.component'); + // let table = null; - +let isAdmin = cookies.get('admin'); // const loadData = (specifiedVc) => { @@ -45,6 +49,8 @@ const loadData = (specifiedVc) => { formatNumber: formatNumber, yarnWebPortalUri: webportalConfig.yarnWebPortalUri, grafanaUri: webportalConfig.grafanaUri, + isAdmin, + modal: vcModelComponent, }); $('#content-wrapper').html(vcHtml); table = $('#vc-table').dataTable({ @@ -54,6 +60,7 @@ const loadData = (specifiedVc) => { {type: 'natural', targets: [0, 1, 2, 3, 4, 5, 6]}, ], }).api(); + resizeContentWrapper(); }, error: function() { alert('Error when loading data.'); @@ -73,12 +80,174 @@ const formatNumber = (x, precision) => { const resizeContentWrapper = () => { $('#content-wrapper').css({'height': $(window).height() + 'px'}); if (table != null) { - $('.dataTables_scrollBody').css('height', (($(window).height() - 265)) + 'px'); + $('.dataTables_scrollBody').css('height', (($(window).height() - (isAdmin === 'true' ? 335 : 265))) + 'px'); table.columns.adjust().draw(); } }; // +const virtualClusterShow = () => { + $('#virtualClustersList input[name="vcname"]').val(''); + $('#virtualClustersList input[name="capacity"]').val(''); + $('#virtualClustersList').modal('show'); +}; + +// +const virtualClustersAdd = () => { + userAuth.checkToken((token) => { + let vcName = $('#virtualClustersList input[name="vcname"]').val(); + let capacity = $('#virtualClustersList input[name="capacity"]').val(); + if (!vcName) { + $('#virtualClustersList input[name="vcname"]').focus(); + return false; + } + if (!capacity) { + $('#virtualClustersList input[name="capacity"]').focus(); + return false; + } + $.ajax({ + url: `${webportalConfig.restServerUri}/api/v1/virtual-clusters/${vcName}`, + data: JSON.stringify({ + 'vcCapacity': capacity, + }), + headers: { + Authorization: `Bearer ${token}`, + }, + contentType: 'application/json; charset=utf-8', + type: 'PUT', + dataType: 'json', + success: (data) => { + loadData(url.parse(window.location.href, true).query['vcName']); + $('#virtualClustersList').modal('hide'); + alert(data.message); + }, + error: (xhr, textStatus, error) => { + const res = JSON.parse(xhr.responseText); + alert(res.message); + }, + }); + }); +}; + +// +const deleteVcItem = (name) => { + if (name == 'default') return false; + const res = confirm(`Notes:\r1. If there are jobs of this virtual cluster still running, it cannot be deleted.\r2. The capacity of this virtual cluster will be returned to default virtual cluster.\r\rAre you sure to delete ${name}?`); + if (!res) return false; + userAuth.checkToken((token) => { + $.ajax({ + url: `${webportalConfig.restServerUri}/api/v1/virtual-clusters/${name}`, + headers: { + Authorization: `Bearer ${token}`, + }, + contentType: 'application/json; charset=utf-8', + type: 'DELETE', + dataType: 'json', + success: (data) => { + loadData(url.parse(window.location.href, true).query['vcName']); + alert(data.message); + }, + error: (xhr, textStatus, error) => { + const res = JSON.parse(xhr.responseText); + alert(res.message); + }, + }); + }); +}; + +// +const editVcItem = (name, capacity) => { + if (name == 'default') return false; + $('input[name="nameEdit"]').val(name); + $('input[name="capacityEdit"]').val(capacity); + $('#virtualClustersEdit').modal('show'); +}; + +// +const editVcItemPut = (name, capacity) => { + userAuth.checkToken((token) => { + $.ajax({ + url: `${webportalConfig.restServerUri}/api/v1/virtual-clusters/${name}`, + data: JSON.stringify({ + 'vcCapacity': parseInt(capacity), + }), + headers: { + Authorization: `Bearer ${token}`, + }, + contentType: 'application/json; charset=utf-8', + type: 'PUT', + dataType: 'json', + success: (data) => { + $('#virtualClustersEdit').modal('hide'); + loadData(url.parse(window.location.href, true).query['vcName']); + alert(data.message); + }, + error: (xhr, textStatus, error) => { + const res = JSON.parse(xhr.responseText); + alert(res.message); + }, + }); + }); +}; + + +const changeVcState = (name, state) => { + if (isAdmin !== 'true') return false; + if (name === 'default') return false; + userAuth.checkToken((token) => { + const res = confirm(`Do you want to ${state.toLowerCase() == 'running' ? 'stop' : 'activate'} ${name}?`); + if (!res) return false; + $.ajax({ + url: `${webportalConfig.restServerUri}/api/v1/virtual-clusters/${$.trim(name)}/status`, + headers: { + Authorization: `Bearer ${token}`, + }, + data: JSON.stringify({ + 'vcStatus': state.toLowerCase() == 'running' ? 'stopped' : 'running', + }), + contentType: 'application/json; charset=utf-8', + type: 'PUT', + dataType: 'json', + success: (data) => { + loadData(url.parse(window.location.href, true).query['vcName']); + alert(data.message); + }, + error: (xhr, textStatus, error) => { + const res = JSON.parse(xhr.responseText); + alert(res.message); + }, + }); + }); +}; + +const convertState = (name, state) => { + let vcState = ''; + let vcStateChage = ''; + let vcStateOrdinary = ''; + let vcStateTips = ''; + if (state === 'RUNNING') { + vcState = 'Running'; + vcStateChage = `onclick='changeVcState("${name}", "${state}")'`; + } else if (state === 'STOPPED') { + vcState = 'Stopped'; + vcStateChage = `onclick='changeVcState("${name}", "${state}")'`; + } else { + vcState = 'Unknown'; + vcStateChage = ''; + } + if (isAdmin === 'true' && name !== 'default') { + vcStateTips = 'title="Click To Change Status"'; + } else { + vcStateOrdinary = 'state-vc-ordinary'; + } + return `${vcState}`; +}; + +window.virtualClusterShow = virtualClusterShow; +window.deleteVcItem = deleteVcItem; +window.editVcItem = editVcItem; +window.changeVcState = changeVcState; +window.convertState = convertState; $(document).ready(() => { $('#sidebar-menu--vc').addClass('active'); @@ -87,4 +256,15 @@ $(document).ready(() => { }; resizeContentWrapper(); loadData(url.parse(window.location.href, true).query['vcName']); + + // add VC + $(document).on('click', '#virtualClustersListAdd', () => { + virtualClustersAdd(); + }); + + $(document).on('click', '#virtualClustersListEdit', () => { + let name = $('input[name="nameEdit"]').val(); + let capacity = $('input[name="capacityEdit"]').val(); + editVcItemPut(name, capacity); + }); }); diff --git a/src/webportal/src/app/vc/vc.component.scss b/src/webportal/src/app/vc/vc.component.scss index b8760735eb..e3ef35b237 100644 --- a/src/webportal/src/app/vc/vc.component.scss +++ b/src/webportal/src/app/vc/vc.component.scss @@ -14,4 +14,91 @@ .metric-icon-loading { color: silver; -} \ No newline at end of file +} + +.add-vc-btn{ + margin: 10px 0 20px 0; +} + +ul, li{ + margin: 0; + padding: 0; + list-style: none; +} + +.modal-vc-content{ + max-width: 390px; + padding: 30px; + margin: 0 auto; + .edit-group{ + position: relative; + .edit-group-item{ + position: absolute; + top: 10px; + left: -76px; + } + } + .add-vc-fild{ + position: relative; + height: auto; + padding: 10px; + font-size: 16px; + } + .remarks{ + display: block; + margin-top: -8px; + padding-bottom: 20px; + font-size: 12px; + color: #666; + } +} + +.vc-user-head{ + padding-bottom: 6px; + font-weight: bold; + border-bottom: 1px solid #d2d2d2; +} +.vc-user-body{ + .change{ + background: #d1d1d1; + } + .vc-user-item{ + padding: 4px 0; + .center{ + text-align: center; + } + } +} +#vc-table{ + .state-vc{ + display: inline-block; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: 700; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; + cursor: pointer; + } + .state-vc-ordinary{ + cursor: default; + } + .state-running{ + background: #3c8dbc; + color: #fff; + } + .state-stopped{ + background: #d2d6de; + color: #444; + } + .item-btn{ + cursor: pointer; + } + .default{ + color: #c0c0c0; + cursor: default; + } +}