2022-03-30 01:36:59 -05:00
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
2023-01-02 00:23:55 -06:00
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
2022-03-30 01:36:59 -05:00
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
// eslint-disable-next-line react/display-name
2023-05-05 03:25:20 -05:00
import React , { useEffect , useMemo , useState } from 'react' ;
2022-03-30 01:36:59 -05:00
import gettext from 'sources/gettext' ;
import PropTypes from 'prop-types' ;
import getApiInstance from 'sources/api_instance' ;
import PgTable from 'sources/components/PgTable' ;
2023-05-05 03:25:20 -05:00
import { InputCheckbox } from '../../../static/js/components/FormComponents' ;
2022-03-30 01:36:59 -05:00
import { makeStyles } from '@material-ui/core/styles' ;
import url _for from 'sources/url_for' ;
import Graphs from './Graphs' ;
import Notify from '../../../static/js/helpers/Notifier' ;
2023-02-09 22:58:39 -06:00
import { Box , Card , CardContent , CardHeader , Tab , Tabs } from '@material-ui/core' ;
2022-03-30 01:36:59 -05:00
import { PgIconButton } from '../../../static/js/components/Buttons' ;
2022-06-02 07:37:59 -05:00
import CancelIcon from '@material-ui/icons/Cancel' ;
import StopSharpIcon from '@material-ui/icons/StopSharp' ;
import ArrowRightOutlinedIcon from '@material-ui/icons/ArrowRightOutlined' ;
import ArrowDropDownOutlinedIcon from '@material-ui/icons/ArrowDropDownOutlined' ;
2022-03-30 01:36:59 -05:00
import WelcomeDashboard from './WelcomeDashboard' ;
import ActiveQuery from './ActiveQuery.ui' ;
import _ from 'lodash' ;
2022-06-02 07:37:59 -05:00
import CachedOutlinedIcon from '@material-ui/icons/CachedOutlined' ;
2022-04-05 01:40:51 -05:00
import EmptyPanelMessage from '../../../static/js/components/EmptyPanelMessage' ;
2022-04-14 00:41:45 -05:00
import TabPanel from '../../../static/js/components/TabPanel' ;
2023-09-28 04:53:15 -05:00
import Summary from './SystemStats/Summary' ;
import CPU from './SystemStats/CPU' ;
import Memory from './SystemStats/Memory' ;
import Storage from './SystemStats/Storage' ;
2022-03-30 01:36:59 -05:00
function parseData ( data ) {
2022-09-08 04:46:48 -05:00
let res = [ ] ;
2022-03-30 01:36:59 -05:00
data . forEach ( ( row ) => {
res . push ( { ... row , icon : '' } ) ;
} ) ;
return res ;
}
const useStyles = makeStyles ( ( theme ) => ( {
emptyPanel : {
height : '100%' ,
background : theme . palette . grey [ 400 ] ,
overflow : 'auto' ,
padding : '8px' ,
display : 'flex' ,
flexDirection : 'column' ,
flexGrow : 1 ,
} ,
fixedSizeList : {
overflowX : 'hidden !important' ,
overflow : 'overlay !important' ,
height : 'auto !important' ,
} ,
dashboardPanel : {
height : '100%' ,
background : theme . palette . grey [ 400 ] ,
} ,
cardHeader : {
padding : '0.25rem 0.5rem' ,
fontWeight : 'bold' ,
backgroundColor : theme . otherVars . tableBg ,
borderBottom : '1px solid' ,
borderBottomColor : theme . otherVars . borderColor ,
} ,
searchPadding : {
display : 'flex' ,
flex : 2.5 ,
} ,
component : {
padding : '8px' ,
} ,
searchInput : {
flex : 1 ,
} ,
panelIcon : {
width : '80%' ,
margin : '0 auto' ,
marginTop : '25px !important' ,
position : 'relative' ,
textAlign : 'center' ,
} ,
panelMessage : {
marginLeft : '0.5rem' ,
fontSize : '0.875rem' ,
} ,
panelContent : {
... theme . mixins . panelBorder ,
2022-04-04 08:33:50 -05:00
display : 'flex' ,
2022-03-30 01:36:59 -05:00
flexDirection : 'column' ,
overflow : 'hidden !important' ,
2022-04-04 08:33:50 -05:00
height : '100%' ,
minHeight : '400px'
2022-04-04 07:12:42 -05:00
} ,
arrowButton : {
fontSize : '2rem !important' ,
margin : '-7px'
2022-03-30 01:36:59 -05:00
} ,
terminateButton : {
color : theme . palette . error . main
} ,
buttonClick : {
backgroundColor : theme . palette . grey [ 400 ]
2022-04-04 07:12:42 -05:00
} ,
refreshButton : {
marginLeft : 'auto' ,
height : '1.9rem' ,
width : '2.2rem' ,
... theme . mixins . panelBorder ,
2023-02-09 22:58:39 -06:00
} ,
chartCard : {
border : '1px solid ' + theme . otherVars . borderColor ,
} ,
chartCardContent : {
padding : '0.25rem 0.5rem' ,
height : '165px' ,
display : 'flex' ,
} ,
chartLegend : {
marginLeft : 'auto' ,
'& > div' : {
display : 'flex' ,
fontWeight : 'normal' ,
2023-10-11 00:57:21 -05:00
flexWrap : 'wrap' ,
2023-02-09 22:58:39 -06:00
'& .legend-value' : {
marginLeft : '4px' ,
'& .legend-label' : {
marginLeft : '4px' ,
}
}
}
2022-03-30 01:36:59 -05:00
}
} ) ) ;
/* eslint-disable react/display-name */
export default function Dashboard ( {
nodeData ,
node ,
item ,
pgBrowser ,
preferences ,
sid ,
did ,
treeNodeInfo ,
... props
} ) {
const classes = useStyles ( ) ;
2022-08-18 03:11:27 -05:00
let tabs = [ gettext ( 'Sessions' ) , gettext ( 'Locks' ) , gettext ( 'Prepared Transactions' ) ] ;
2023-09-27 05:34:48 -05:00
let mainTabs = [ gettext ( 'General' ) , gettext ( 'System Statistics' ) ] ;
let systemStatsTabs = [ gettext ( 'Summary' ) , gettext ( 'CPU' ) , gettext ( 'Memory' ) , gettext ( 'Storage' ) ] ;
2022-03-30 01:36:59 -05:00
const [ dashData , setdashData ] = useState ( [ ] ) ;
const [ msg , setMsg ] = useState ( '' ) ;
2023-09-27 05:34:48 -05:00
const [ ssMsg , setSsMsg ] = useState ( '' ) ;
2022-04-14 00:41:45 -05:00
const [ tabVal , setTabVal ] = useState ( 0 ) ;
2023-09-27 05:34:48 -05:00
const [ mainTabVal , setMainTabVal ] = useState ( 0 ) ;
2022-04-11 07:12:16 -05:00
const [ refresh , setRefresh ] = useState ( false ) ;
2023-05-05 03:25:20 -05:00
const [ activeOnly , setActiveOnly ] = useState ( false ) ;
2022-03-30 01:36:59 -05:00
const [ schemaDict , setSchemaDict ] = React . useState ( { } ) ;
2023-09-27 05:34:48 -05:00
const [ systemStatsTabVal , setSystemStatsTabVal ] = useState ( 0 ) ;
2023-10-04 04:34:33 -05:00
const [ ldid , setLdid ] = useState ( 0 ) ;
2023-09-27 05:34:48 -05:00
const systemStatsTabChanged = ( e , tabVal ) => {
setSystemStatsTabVal ( tabVal ) ;
} ;
2022-03-30 01:36:59 -05:00
if ( ! did ) {
2022-08-18 03:11:27 -05:00
tabs . push ( gettext ( 'Configuration' ) ) ;
2022-03-30 01:36:59 -05:00
}
2022-09-10 03:34:28 -05:00
2022-03-30 01:36:59 -05:00
const tabChanged = ( e , tabVal ) => {
2022-04-14 00:41:45 -05:00
setTabVal ( tabVal ) ;
2022-03-30 01:36:59 -05:00
} ;
2023-09-27 05:34:48 -05:00
const mainTabChanged = ( e , tabVal ) => {
setMainTabVal ( tabVal ) ;
} ;
2022-03-30 01:36:59 -05:00
const serverConfigColumns = [
{
accessor : 'name' ,
Header : gettext ( 'Name' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-03-30 01:36:59 -05:00
resizable : true ,
disableGlobalFilter : false ,
minWidth : 50 ,
2022-05-18 07:19:54 -05:00
width : 100 ,
minResizeWidth : 150 ,
2022-03-30 01:36:59 -05:00
} ,
{
accessor : 'category' ,
Header : gettext ( 'Category' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-03-30 01:36:59 -05:00
resizable : true ,
disableGlobalFilter : false ,
minWidth : 50 ,
} ,
{
accessor : 'setting' ,
2022-04-04 07:12:42 -05:00
Header : gettext ( 'Value' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-03-30 01:36:59 -05:00
resizable : true ,
disableGlobalFilter : false ,
minWidth : 50 ,
2022-05-18 07:19:54 -05:00
width : 100 ,
2022-03-30 01:36:59 -05:00
} ,
{
accessor : 'unit' ,
Header : gettext ( 'Unit' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-03-30 01:36:59 -05:00
resizable : true ,
disableGlobalFilter : false ,
minWidth : 26 ,
2022-05-18 07:19:54 -05:00
width : 30 ,
2022-03-30 01:36:59 -05:00
} ,
{
accessor : 'short_desc' ,
Header : gettext ( 'Description' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
} ,
] ;
const activityColumns = [
{
accessor : 'terminate_query' ,
Header : ( ) => null ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : false ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
2022-04-05 01:40:51 -05:00
width : 35 ,
minWidth : 0 ,
2022-03-30 01:36:59 -05:00
id : 'btn-terminate' ,
// eslint-disable-next-line react/display-name
Cell : ( { row } ) => {
2022-09-08 04:46:48 -05:00
let terminate _session _url =
2022-03-30 01:36:59 -05:00
url _for ( 'dashboard.index' ) + 'terminate_session' + '/' + sid ,
title = gettext ( 'Terminate Session?' ) ,
txtConfirm = gettext (
'Are you sure you wish to terminate the session?'
) ,
txtSuccess = gettext ( 'Session terminated successfully.' ) ,
txtError = gettext (
'An error occurred whilst terminating the active query.'
) ;
const action _url = did
? terminate _session _url + '/' + did
: terminate _session _url ;
const api = getApiInstance ( ) ;
return (
< PgIconButton
size = "xs"
noBorder
icon = { < CancelIcon / > }
className = { classes . terminateButton }
onClick = { ( ) => {
if (
! canTakeAction ( row , 'terminate' )
)
return ;
let url = action _url + '/' + row . values . pid ;
Notify . confirm (
title ,
txtConfirm ,
function ( ) {
api
. delete ( url )
. then ( function ( res ) {
if ( res . data == gettext ( 'Success' ) ) {
Notify . success ( txtSuccess ) ;
2022-04-11 07:12:16 -05:00
setRefresh ( ! refresh ) ;
2022-03-30 01:36:59 -05:00
} else {
Notify . error ( txtError ) ;
}
} )
. catch ( function ( error ) {
Notify . alert (
gettext ( 'Failed to retrieve data from the server.' ) ,
2022-08-18 03:11:27 -05:00
error . message
2022-03-30 01:36:59 -05:00
) ;
} ) ;
} ,
function ( ) {
return true ;
}
) ;
} }
color = "default"
aria - label = "Terminate Session?"
title = { gettext ( 'Terminate Session?' ) }
> < / PgIconButton >
) ;
} ,
} ,
{
accessor : 'cancel_Query' ,
Header : ( ) => null ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : false ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
2022-04-05 01:40:51 -05:00
width : 35 ,
minWidth : 0 ,
2022-03-30 01:36:59 -05:00
id : 'btn-cancel' ,
Cell : ( { row } ) => {
2022-09-08 04:46:48 -05:00
let cancel _query _url =
2022-03-30 01:36:59 -05:00
url _for ( 'dashboard.index' ) + 'cancel_query' + '/' + sid ,
title = gettext ( 'Cancel Active Query?' ) ,
txtConfirm = gettext (
'Are you sure you wish to cancel the active query?'
) ,
txtSuccess = gettext ( 'Active query cancelled successfully.' ) ,
txtError = gettext (
'An error occurred whilst cancelling the active query.'
) ;
const action _url = did ? cancel _query _url + '/' + did : cancel _query _url ;
const api = getApiInstance ( ) ;
return (
< PgIconButton
size = "xs"
noBorder
2022-06-02 07:37:59 -05:00
icon = { < StopSharpIcon / > }
2022-03-30 01:36:59 -05:00
onClick = { ( ) => {
if ( ! canTakeAction ( row , 'cancel' ) )
return ;
let url = action _url + '/' + row . values . pid ;
Notify . confirm (
title ,
txtConfirm ,
function ( ) {
api
. delete ( url )
. then ( function ( res ) {
if ( res . data == gettext ( 'Success' ) ) {
Notify . success ( txtSuccess ) ;
2022-04-14 00:41:45 -05:00
setRefresh ( ! refresh ) ;
2022-03-30 01:36:59 -05:00
} else {
Notify . error ( txtError ) ;
2022-04-14 00:41:45 -05:00
setRefresh ( ! refresh ) ;
2022-03-30 01:36:59 -05:00
}
} )
. catch ( function ( error ) {
Notify . alert (
gettext ( 'Failed to retrieve data from the server.' ) ,
2022-08-18 03:11:27 -05:00
error . message
2022-03-30 01:36:59 -05:00
) ;
} ) ;
} ,
function ( ) {
return true ;
}
) ;
} }
color = "default"
aria - label = "Cancel the query"
title = { gettext ( 'Cancel the active query' ) }
> < / PgIconButton >
) ;
} ,
} ,
{
accessor : 'view_active_query' ,
Header : ( ) => null ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : false ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
2022-04-05 01:40:51 -05:00
width : 35 ,
minWidth : 0 ,
2022-03-30 01:36:59 -05:00
id : 'btn-edit' ,
Cell : ( { row } ) => {
let canEditRow = true ;
return (
< PgIconButton
2022-04-04 08:33:50 -05:00
size = "xs"
2022-03-30 01:36:59 -05:00
className = { row . isExpanded ? classes . buttonClick : '' }
icon = {
row . isExpanded ? (
2022-04-04 07:12:42 -05:00
< ArrowDropDownOutlinedIcon className = { classes . arrowButton } / >
2022-03-30 01:36:59 -05:00
) : (
2022-04-04 07:12:42 -05:00
< ArrowRightOutlinedIcon className = { classes . arrowButton } / >
2022-03-30 01:36:59 -05:00
)
}
noBorder
onClick = { ( e ) => {
e . preventDefault ( ) ;
row . toggleRowExpanded ( ! row . isExpanded ) ;
2022-04-28 01:40:42 -05:00
let schema = new ActiveQuery ( {
query : row . original . query ,
backend _type : row . original . backend _type ,
state _change : row . original . state _change ,
query _start : row . original . query _start ,
} ) ;
setSchemaDict ( prevState => ( {
... prevState ,
[ row . id ] : schema
} ) ) ;
2022-03-30 01:36:59 -05:00
} }
disabled = { ! canEditRow }
aria - label = "View the active session details"
title = { gettext ( 'View the active session details' ) }
/ >
) ;
} ,
} ,
{
accessor : 'pid' ,
Header : gettext ( 'PID' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
minWidth : 26 ,
2022-05-18 07:19:54 -05:00
width : 60 ,
2022-03-30 01:36:59 -05:00
} ,
{
accessor : 'datname' ,
Header : gettext ( 'Database' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
minWidth : 26 ,
2022-05-18 07:19:54 -05:00
width : 80 ,
2022-03-30 01:36:59 -05:00
isVisible : ! did ? true : false
} ,
{
accessor : 'usename' ,
Header : gettext ( 'User' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
minWidth : 26 ,
2022-05-18 07:19:54 -05:00
width : 60
2022-03-30 01:36:59 -05:00
} ,
{
accessor : 'application_name' ,
Header : gettext ( 'Application' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
minWidth : 26 ,
} ,
{
accessor : 'client_addr' ,
Header : gettext ( 'Client' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
minWidth : 26 ,
} ,
{
accessor : 'backend_start' ,
Header : gettext ( 'Backend start' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
2022-05-18 07:19:54 -05:00
minWidth : 100 ,
2022-03-30 01:36:59 -05:00
} ,
{
accessor : 'xact_start' ,
Header : gettext ( 'Transaction start' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
minWidth : 26 ,
} ,
{
accessor : 'state' ,
Header : gettext ( 'State' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
minWidth : 26 ,
2022-05-18 07:19:54 -05:00
width : 40
2022-03-30 01:36:59 -05:00
} ,
{
accessor : 'waiting' ,
Header : gettext ( 'Waiting' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
isVisible : treeNodeInfo ? . server ? . version < 90600
} ,
{
accessor : 'wait_event' ,
Header : gettext ( 'Wait event' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
} ,
{
accessor : 'blocking_pids' ,
Header : gettext ( 'Blocking PIDs' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
} ,
] ;
const databaseLocksColumns = [
{
accessor : 'pid' ,
Header : gettext ( 'PID' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
minWidth : 26 ,
2022-05-18 07:19:54 -05:00
width : 50 ,
2022-03-30 01:36:59 -05:00
} ,
{
accessor : 'datname' ,
Header : gettext ( 'Database' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
minWidth : 26 ,
2022-05-18 07:19:54 -05:00
isVisible : ! did ? true : false ,
width : 80
2022-03-30 01:36:59 -05:00
} ,
{
accessor : 'locktype' ,
Header : gettext ( 'Lock type' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
minWidth : 26 ,
2022-05-18 07:19:54 -05:00
width : 80 ,
2022-03-30 01:36:59 -05:00
} ,
{
accessor : 'relation' ,
Header : gettext ( 'Target relation' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
} ,
{
accessor : 'page' ,
Header : gettext ( 'Page' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
minWidth : 26 ,
2022-05-18 07:19:54 -05:00
width : 80 ,
2022-03-30 01:36:59 -05:00
} ,
{
accessor : 'tuple' ,
Header : gettext ( 'Tuple' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
minWidth : 26 ,
} ,
{
accessor : 'virtualxid' ,
Header : gettext ( 'vXID (target)' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
2022-05-18 07:19:54 -05:00
minWidth : 50 ,
width : 80
2022-03-30 01:36:59 -05:00
} ,
{
accessor : 'transactionid' ,
Header : gettext ( 'XID (target)' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
minWidth : 50 ,
2022-05-18 07:19:54 -05:00
width : 80 ,
2022-03-30 01:36:59 -05:00
} ,
{
accessor : 'classid' ,
Header : gettext ( 'Class' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
minWidth : 26 ,
2022-05-18 07:19:54 -05:00
width : 80 ,
2022-03-30 01:36:59 -05:00
} ,
{
accessor : 'objid' ,
Header : gettext ( 'Object ID' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
minWidth : 50 ,
2022-05-18 07:19:54 -05:00
width : 80 ,
2022-03-30 01:36:59 -05:00
} ,
{
accessor : 'virtualtransaction' ,
Header : gettext ( 'vXID (owner)' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
minWidth : 50 ,
} ,
{
accessor : 'mode' ,
Header : gettext ( 'Mode' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
} ,
{
id : 'granted' ,
accessor : 'granted' ,
Header : gettext ( 'Granted?' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
2022-05-18 07:19:54 -05:00
minWidth : 30 ,
width : 80 ,
2022-03-30 01:36:59 -05:00
Cell : ( { value } ) => String ( value )
} ,
] ;
const databasePreparedColumns = [
{
accessor : 'git' ,
Header : gettext ( 'Name' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
} ,
{
accessor : 'datname' ,
Header : gettext ( 'Database' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
minWidth : 26 ,
2022-05-18 07:19:54 -05:00
width : 80 ,
2022-03-30 01:36:59 -05:00
isVisible : ! did ? true : false
} ,
{
accessor : 'Owner' ,
Header : gettext ( 'Owner' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
} ,
{
accessor : 'transaction' ,
Header : gettext ( 'XID' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
} ,
{
accessor : 'prepared' ,
Header : gettext ( 'Prepared at' ) ,
2022-08-11 00:19:45 -05:00
sortable : true ,
2022-05-18 07:19:54 -05:00
resizable : true ,
2022-03-30 01:36:59 -05:00
disableGlobalFilter : false ,
} ,
] ;
const canTakeAction = ( row , cellAction ) => {
// We will validate if user is allowed to cancel the active query
// If there is only one active session means it probably our main
// connection session
cellAction = cellAction || null ;
2022-09-08 04:46:48 -05:00
let pg _version = treeNodeInfo . server . version || null ,
2022-03-30 01:36:59 -05:00
is _cancel _session = cellAction === 'cancel' ,
txtMessage ,
maintenance _database = treeNodeInfo . server . db ,
is _super _user ,
current _user ;
2022-09-08 04:46:48 -05:00
let can _signal _backend =
2022-03-30 01:36:59 -05:00
treeNodeInfo . server && treeNodeInfo . server . user
? treeNodeInfo . server . user . can _signal _backend
: false ;
if (
treeNodeInfo . server &&
treeNodeInfo . server . user &&
treeNodeInfo . server . user . is _superuser
) {
is _super _user = true ;
} else {
is _super _user = false ;
current _user =
treeNodeInfo . server && treeNodeInfo . server . user
? treeNodeInfo . server . user . name
: null ;
}
// With PG10, We have background process showing on dashboard
// We will not allow user to cancel them as they will fail with error
// anyway, so better usability we will throw our on notification
// Background processes do not have database field populated
if ( pg _version && pg _version >= 100000 && ! row . original . datname ) {
if ( is _cancel _session ) {
txtMessage = gettext ( 'You cannot cancel background worker processes.' ) ;
} else {
txtMessage = gettext (
'You cannot terminate background worker processes.'
) ;
}
Notify . info ( txtMessage ) ;
return false ;
// If it is the last active connection on maintenance db then error out
} else if (
maintenance _database == row . original . datname &&
row . original . state == 'active'
) {
if ( is _cancel _session ) {
txtMessage = gettext (
'You are not allowed to cancel the main active session.'
) ;
} else {
txtMessage = gettext (
'You are not allowed to terminate the main active session.'
) ;
}
Notify . error ( txtMessage ) ;
return false ;
} else if ( is _cancel _session && row . original . state == 'idle' ) {
// If this session is already idle then do nothing
Notify . info ( gettext ( 'The session is already in idle state.' ) ) ;
return false ;
} else if ( can _signal _backend ) {
// user with membership of 'pg_signal_backend' can terminate the session of non admin user.
return true ;
} else if ( is _super _user ) {
// Super user can do anything
return true ;
2023-10-11 03:13:31 -05:00
} else if ( current _user && current _user == treeNodeInfo . server . user ? . name ) {
2022-03-30 01:36:59 -05:00
// Non-super user can cancel only their active queries
return true ;
} else {
// Do not allow to cancel someone else session to non-super user
if ( is _cancel _session ) {
txtMessage = gettext (
'Superuser privileges are required to cancel another users query.'
) ;
} else {
txtMessage = gettext (
'Superuser privileges are required to terminate another users query.'
) ;
}
Notify . error ( txtMessage ) ;
return false ;
}
} ;
2022-04-14 00:41:45 -05:00
useEffect ( ( ) => {
// Reset Tab values to 0, so that it will select "Sessions" on node changed.
nodeData ? . _type === 'database' && setTabVal ( 0 ) ;
} , [ nodeData ] ) ;
2022-03-30 01:36:59 -05:00
useEffect ( ( ) => {
let url ,
2023-09-27 05:34:48 -05:00
ssExtensionCheckUrl = url _for ( 'dashboard.check_system_statistics' ) ,
2022-03-30 01:36:59 -05:00
message = gettext (
'Please connect to the selected server to view the dashboard.'
) ;
2022-09-10 03:34:28 -05:00
if ( tabVal == 3 && did ) {
setTabVal ( 0 ) ;
}
2022-03-30 01:36:59 -05:00
if ( sid && props . serverConnected ) {
2022-04-14 00:41:45 -05:00
if ( tabVal === 0 ) {
2022-03-30 01:36:59 -05:00
url = url _for ( 'dashboard.activity' ) ;
2022-04-14 00:41:45 -05:00
} else if ( tabVal === 1 ) {
2022-03-30 01:36:59 -05:00
url = url _for ( 'dashboard.locks' ) ;
2022-04-14 00:41:45 -05:00
} else if ( tabVal === 2 ) {
2022-03-30 01:36:59 -05:00
url = url _for ( 'dashboard.prepared' ) ;
} else {
url = url _for ( 'dashboard.config' ) ;
}
message = gettext ( 'Loading dashboard...' ) ;
2022-04-05 03:10:22 -05:00
if ( did && ! props . dbConnected ) return ;
2022-03-30 01:36:59 -05:00
if ( did ) url += sid + '/' + did ;
else url += sid ;
2023-09-27 05:34:48 -05:00
if ( did && ! props . dbConnected ) return ;
2023-10-04 04:34:33 -05:00
if ( did && did > 0 ) ssExtensionCheckUrl += '/' + sid + '/' + did ;
2023-09-27 05:34:48 -05:00
else ssExtensionCheckUrl += '/' + sid ;
2022-03-30 01:36:59 -05:00
const api = getApiInstance ( ) ;
if ( node ) {
2023-10-04 04:34:33 -05:00
if ( mainTabVal == 0 ) {
api ( {
url : url ,
type : 'GET' ,
2022-03-30 01:36:59 -05:00
} )
2023-10-04 04:34:33 -05:00
. then ( ( res ) => {
setdashData ( parseData ( res . data ) ) ;
} )
. catch ( ( error ) => {
Notify . alert (
gettext ( 'Failed to retrieve data from the server.' ) ,
_ . isUndefined ( error . response ) ? error . message : error . response . data . errormsg
) ;
// show failed message.
setMsg ( gettext ( 'Failed to retrieve data from the server.' ) ) ;
} ) ;
}
else if ( mainTabVal == 1 ) {
api ( {
url : ssExtensionCheckUrl ,
type : 'GET' ,
2023-09-27 05:34:48 -05:00
} )
2023-10-04 04:34:33 -05:00
. then ( ( res ) => {
const data = res . data ;
if ( data [ 'ss_present' ] == false ) {
setSsMsg ( gettext ( 'System stats extension is not installed. You can install the extension in a database using the "CREATE EXTENSION system_stats;" SQL command. Reload the pgAdmin once you installed.' ) ) ;
setLdid ( 0 ) ;
} else {
setSsMsg ( 'installed' ) ;
setLdid ( did ) ;
}
} )
. catch ( ( ) => {
setSsMsg ( gettext ( 'Failed to verify the presence of system stats extension.' ) ) ;
setLdid ( 0 ) ;
} ) ;
} else {
setSsMsg ( '' ) ;
setLdid ( 0 ) ;
}
2022-03-30 01:36:59 -05:00
} else {
setMsg ( message ) ;
}
}
if ( message != '' ) {
setMsg ( message ) ;
}
2023-10-04 04:34:33 -05:00
} , [ nodeData , tabVal , did , preferences , refresh , props . dbConnected , mainTabVal ] ) ;
2022-04-04 07:12:42 -05:00
2023-05-05 03:25:20 -05:00
const filteredDashData = useMemo ( ( ) => {
if ( tabVal == 0 && activeOnly ) {
// we want to show 'idle in transaction', 'active', 'active in transaction', and future non-blank, non-"idle" status values
return dashData . filter ( ( r ) => ( r . state && r . state != '' && r . state != 'idle' ) ) ;
}
return dashData ;
} , [ dashData , activeOnly , tabVal ] ) ;
2022-04-04 07:12:42 -05:00
const RefreshButton = ( ) => {
return (
< PgIconButton
size = "xs"
noBorder
className = { classes . refreshButton }
2022-06-02 07:37:59 -05:00
icon = { < CachedOutlinedIcon / > }
2022-04-04 07:12:42 -05:00
onClick = { ( e ) => {
e . preventDefault ( ) ;
2022-04-11 07:12:16 -05:00
setRefresh ( ! refresh ) ;
2022-04-04 07:12:42 -05:00
} }
color = "default"
aria - label = "Refresh"
title = { gettext ( 'Refresh' ) }
> < / PgIconButton >
) ;
} ;
2022-03-30 01:36:59 -05:00
2022-09-10 03:30:22 -05:00
const showDefaultContents = ( ) => {
return (
sid && ! props . serverConnected ? (
< Box className = { classes . dashboardPanel } >
< div className = { classes . emptyPanel } >
< EmptyPanelMessage text = { msg } / >
< / div >
< / Box >
) : (
< WelcomeDashboard
pgBrowser = { pgBrowser }
node = { node }
itemData = { nodeData }
item = { item }
sid = { sid }
did = { did }
/ >
)
) ;
} ;
2023-05-05 03:25:20 -05:00
const CustomActiveOnlyHeaderLabel =
{
label : gettext ( 'Active sessions only' ) ,
} ;
const CustomActiveOnlyHeader = ( ) => {
return (
< InputCheckbox
label = { gettext ( 'Active sessions only' ) }
labelPlacement = "end"
className = { classes . searchInput }
onChange = { ( e ) => {
e . preventDefault ( ) ;
setActiveOnly ( e . target . checked ) ;
} }
value = { activeOnly }
controlProps = { CustomActiveOnlyHeaderLabel }
> < / InputCheckbox > ) ;
} ;
2023-06-12 08:14:31 -05:00
2022-03-30 01:36:59 -05:00
return (
< >
{ sid && props . serverConnected ? (
< Box className = { classes . dashboardPanel } >
< Box className = { classes . emptyPanel } >
2023-09-27 05:34:48 -05:00
< Box className = { classes . panelContent } >
< Box height = "100%" display = "flex" flexDirection = "column" >
< Box >
< Tabs
value = { mainTabVal }
onChange = { mainTabChanged }
>
{ mainTabs . map ( ( tabValue ) => {
return < Tab key = { tabValue } label = { tabValue } / > ;
} ) }
< / Tabs >
2022-06-23 07:45:16 -05:00
< / Box >
2023-09-27 05:34:48 -05:00
{ /* General Statistics */ }
< TabPanel value = { mainTabVal } index = { 0 } classNameRoot = { classes . tabPanel } >
{ ! _ . isUndefined ( preferences ) && preferences . show _graphs && (
< Graphs
key = { sid + did }
preferences = { preferences }
sid = { sid }
did = { did }
pageVisible = { props . panelVisible }
> < / Graphs >
) }
{ ! _ . isUndefined ( preferences ) && preferences . show _activity && (
< Box className = { classes . panelContent } >
< Box
className = { classes . cardHeader }
title = { props . dbConnected ? gettext ( 'Database activity' ) : gettext ( 'Server activity' ) }
>
{ props . dbConnected ? gettext ( 'Database activity' ) : gettext ( 'Server activity' ) } { ' ' }
< / Box >
< Box height = "100%" display = "flex" flexDirection = "column" >
< Box >
< Tabs
value = { tabVal }
onChange = { tabChanged }
>
{ tabs . map ( ( tabValue ) => {
return < Tab key = { tabValue } label = { tabValue } / > ;
} ) }
< RefreshButton / >
< / Tabs >
< / Box >
< TabPanel value = { tabVal } index = { 0 } classNameRoot = { classes . tabPanel } >
< PgTable
caveTable = { false }
CustomHeader = { CustomActiveOnlyHeader }
columns = { activityColumns }
data = { filteredDashData }
schema = { schemaDict }
> < / PgTable >
< / TabPanel >
< TabPanel value = { tabVal } index = { 1 } classNameRoot = { classes . tabPanel } >
< PgTable
caveTable = { false }
columns = { databaseLocksColumns }
data = { dashData }
> < / PgTable >
< / TabPanel >
< TabPanel value = { tabVal } index = { 2 } classNameRoot = { classes . tabPanel } >
< PgTable
caveTable = { false }
columns = { databasePreparedColumns }
data = { dashData }
> < / PgTable >
< / TabPanel >
< TabPanel value = { tabVal } index = { 3 } classNameRoot = { classes . tabPanel } >
< PgTable
caveTable = { false }
columns = { serverConfigColumns }
data = { dashData }
> < / PgTable >
< / TabPanel >
< / Box >
< / Box >
) }
< / TabPanel >
{ /* System Statistics */ }
< TabPanel value = { mainTabVal } index = { 1 } classNameRoot = { classes . tabPanel } >
< Box height = "100%" display = "flex" flexDirection = "column" >
2023-10-04 04:34:33 -05:00
{ ssMsg === 'installed' && did === ldid ?
2023-09-27 05:34:48 -05:00
< >
< Box >
< Tabs
value = { systemStatsTabVal }
onChange = { systemStatsTabChanged }
>
{ systemStatsTabs . map ( ( tabValue ) => {
return < Tab key = { tabValue } label = { tabValue } / > ;
} ) }
< / Tabs >
< / Box >
< TabPanel value = { systemStatsTabVal } index = { 0 } classNameRoot = { classes . tabPanel } >
< Summary
key = { sid + did }
preferences = { preferences }
sid = { sid }
did = { did }
pageVisible = { props . panelVisible }
serverConnected = { props . serverConnected }
/ >
< / TabPanel >
< TabPanel value = { systemStatsTabVal } index = { 1 } classNameRoot = { classes . tabPanel } >
< CPU
key = { sid + did }
preferences = { preferences }
sid = { sid }
did = { did }
pageVisible = { props . panelVisible }
serverConnected = { props . serverConnected }
/ >
< / TabPanel >
< TabPanel value = { systemStatsTabVal } index = { 2 } classNameRoot = { classes . tabPanel } >
< Memory
key = { sid + did }
preferences = { preferences }
sid = { sid }
did = { did }
pageVisible = { props . panelVisible }
serverConnected = { props . serverConnected }
/ >
< / TabPanel >
< TabPanel value = { systemStatsTabVal } index = { 3 } classNameRoot = { classes . tabPanel } >
< Storage
key = { sid + did }
preferences = { preferences }
sid = { sid }
did = { did }
pageVisible = { props . panelVisible }
serverConnected = { props . serverConnected }
systemStatsTabVal = { systemStatsTabVal }
/ >
< / TabPanel >
< / > :
< div className = { classes . emptyPanel } >
< EmptyPanelMessage text = { ssMsg } / >
< / div >
}
2022-06-23 07:45:16 -05:00
< / Box >
2023-09-27 05:34:48 -05:00
< / TabPanel >
2022-04-04 08:33:50 -05:00
< / Box >
2023-09-27 05:34:48 -05:00
< / Box >
2022-03-30 01:36:59 -05:00
< / Box >
< / Box >
2022-09-10 03:30:22 -05:00
) : showDefaultContents ( ) }
2022-03-30 01:36:59 -05:00
< / >
) ;
}
Dashboard . propTypes = {
node : PropTypes . func ,
itemData : PropTypes . object ,
nodeData : PropTypes . object ,
treeNodeInfo : PropTypes . object ,
item : PropTypes . object ,
pgBrowser : PropTypes . object ,
preferences : PropTypes . object ,
sid : PropTypes . string ,
did : PropTypes . oneOfType ( [ PropTypes . bool , PropTypes . number ] ) ,
row : PropTypes . object ,
serverConnected : PropTypes . bool ,
2022-04-05 03:10:22 -05:00
dbConnected : PropTypes . bool ,
2022-07-04 01:33:18 -05:00
panelVisible : PropTypes . bool ,
2022-03-30 01:36:59 -05:00
} ;
export function ChartContainer ( props ) {
2023-02-09 22:58:39 -06:00
const classes = useStyles ( ) ;
2022-03-30 01:36:59 -05:00
return (
2023-02-09 22:58:39 -06:00
< Card className = { classes . chartCard } elevation = { 0 } >
< CardHeader title = { < Box display = "flex" justifyContent = "space-between" >
< div id = { props . id } > { props . title } < / div >
< div className = { classes . chartLegend } >
< div className = "d-flex" >
2023-06-12 08:14:31 -05:00
{ props . datasets ? . map ( ( datum ) => (
< div className = "legend-value" key = { datum . label } >
2023-02-09 22:58:39 -06:00
< span style = { { backgroundColor : datum . borderColor } } > & nbsp ; & nbsp ; & nbsp ; & nbsp ; < / span >
< span className = "legend-label" > { datum . label } < / span >
< / div >
) ) }
2023-02-06 04:25:02 -06:00
< / div >
2022-03-30 01:36:59 -05:00
< / div >
2023-02-09 22:58:39 -06:00
< / Box > } / >
< CardContent className = { classes . chartCardContent } >
2023-02-10 03:17:29 -06:00
{ ! props . errorMsg && ! props . isTest && props . children }
2023-02-18 04:16:37 -06:00
{ props . errorMsg && < EmptyPanelMessage text = { props . errorMsg } / > }
2023-02-09 22:58:39 -06:00
< / CardContent >
< / Card >
2022-03-30 01:36:59 -05:00
) ;
}
ChartContainer . propTypes = {
id : PropTypes . string . isRequired ,
title : PropTypes . string . isRequired ,
2023-02-06 04:25:02 -06:00
datasets : PropTypes . array . isRequired ,
2022-03-30 01:36:59 -05:00
children : PropTypes . node . isRequired ,
errorMsg : PropTypes . string ,
2023-02-06 04:25:02 -06:00
isTest : PropTypes . bool
2022-03-30 01:36:59 -05:00
} ;