mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2024-11-22 08:46:39 -06:00
Port About dialog to React. Fixes #7567
This commit is contained in:
parent
a7fd7d67b3
commit
81f52a82c8
@ -11,6 +11,7 @@ notes for it.
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
release_notes_6_13
|
||||
release_notes_6_12
|
||||
release_notes_6_11
|
||||
release_notes_6_10
|
||||
|
20
docs/en_US/release_notes_6_13.rst
Normal file
20
docs/en_US/release_notes_6_13.rst
Normal file
@ -0,0 +1,20 @@
|
||||
************
|
||||
Version 6.13
|
||||
************
|
||||
|
||||
Release date: 2022-08-25
|
||||
|
||||
This release contains a number of bug fixes and new features since the release of pgAdmin 4 v6.12.
|
||||
|
||||
New features
|
||||
************
|
||||
|
||||
|
||||
Housekeeping
|
||||
************
|
||||
|
||||
| `Issue #7567 <https://redmine.postgresql.org/issues/7567>`_ - Port About dialog to React.
|
||||
|
||||
Bug fixes
|
||||
*********
|
||||
|
@ -9,12 +9,13 @@
|
||||
|
||||
"""A blueprint module implementing the about box."""
|
||||
|
||||
from flask import Response, render_template, url_for, request
|
||||
from flask import Response, render_template, request
|
||||
from flask_babel import gettext
|
||||
from flask_security import current_user, login_required
|
||||
from pgadmin.utils import PgAdminModule
|
||||
from pgadmin.utils.menu import MenuItem
|
||||
from pgadmin.utils.constants import MIMETYPE_APP_JS
|
||||
from pgadmin.utils.ajax import make_json_response
|
||||
import config
|
||||
import httpagentparser
|
||||
from pgadmin.model import User
|
||||
@ -66,6 +67,7 @@ def index():
|
||||
info['os_details'] = os_details
|
||||
info['config_db'] = config.SQLITE_PATH
|
||||
info['log_file'] = config.LOG_FILE
|
||||
info['version'] = config.APP_VERSION
|
||||
|
||||
if config.SERVER_MODE:
|
||||
info['app_mode'] = gettext('Server')
|
||||
@ -98,8 +100,9 @@ def index():
|
||||
|
||||
info['settings'] = settings
|
||||
|
||||
return render_template(
|
||||
MODULE_NAME + '/index.html', info=info, _=gettext
|
||||
return make_json_response(
|
||||
data=info,
|
||||
status=200
|
||||
)
|
||||
|
||||
|
||||
|
151
web/pgadmin/about/static/js/AboutComponent.jsx
Normal file
151
web/pgadmin/about/static/js/AboutComponent.jsx
Normal file
@ -0,0 +1,151 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import gettext from 'sources/gettext';
|
||||
import url_for from 'sources/url_for';
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { Box, Grid, InputLabel } from '@material-ui/core';
|
||||
import PropTypes from 'prop-types';
|
||||
import { DefaultButton } from '../../../static/js/components/Buttons';
|
||||
import { makeStyles } from '@material-ui/styles';
|
||||
import { InputText } from '../../../static/js/components/FormComponents';
|
||||
import getApiInstance from '../../../static/js/api_instance';
|
||||
import { copyToClipboard } from '../../../static/js/clipboard';
|
||||
import Notify from '../../../static/js/helpers/Notifier';
|
||||
import { useDelayedCaller } from '../../../static/js/custom_hooks';
|
||||
|
||||
|
||||
const useStyles = makeStyles((theme)=>({
|
||||
container: {
|
||||
padding: '16px',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
copyBtn: {
|
||||
marginRight: '1px',
|
||||
float: 'right',
|
||||
borderColor: theme.otherVars.borderColor,
|
||||
fontSize: '13px',
|
||||
},
|
||||
}));
|
||||
|
||||
export default function AboutComponent() {
|
||||
const classes = useStyles();
|
||||
const containerRef = useRef();
|
||||
const [aboutData, setAboutData] = useState([]);
|
||||
const [copyText, setCopyText] = useState(gettext('Copy'));
|
||||
const revertCopiedText = useDelayedCaller(()=>{
|
||||
setCopyText(gettext('Copy'));
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const about_url = url_for('about.index');
|
||||
const api = getApiInstance();
|
||||
|
||||
api.get(about_url).then((res)=>{
|
||||
setAboutData(res.data.data);
|
||||
}).catch((err)=>{
|
||||
Notify.error(err);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box className={classes.container} ref={containerRef}>
|
||||
<Grid container spacing={0}>
|
||||
<Grid item lg={3} md={3} sm={3} xs={12}>
|
||||
<InputLabel style={{fontWeight: 'bold'}}>{gettext('Version')}</InputLabel>
|
||||
</Grid>
|
||||
<Grid item lg={9} md={9} sm={9} xs={12}>
|
||||
<InputLabel>{aboutData.version}</InputLabel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container spacing={0}>
|
||||
<Grid item lg={3} md={3} sm={3} xs={12}>
|
||||
<InputLabel style={{fontWeight: 'bold'}}>{gettext('Application Mode')}</InputLabel>
|
||||
</Grid>
|
||||
<Grid item lg={9} md={9} sm={9} xs={12}>
|
||||
<InputLabel>{aboutData.app_mode}</InputLabel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container spacing={0}>
|
||||
<Grid item lg={3} md={3} sm={3} xs={12}>
|
||||
<InputLabel style={{fontWeight: 'bold'}}>{gettext('Current User')}</InputLabel>
|
||||
</Grid>
|
||||
<Grid item lg={9} md={9} sm={9} xs={12}>
|
||||
<InputLabel>{aboutData.current_user}</InputLabel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
{ aboutData.nwjs &&
|
||||
<Grid container spacing={0}>
|
||||
<Grid item lg={3} md={3} sm={3} xs={12}>
|
||||
<InputLabel style={{fontWeight: 'bold'}}>{gettext('NW.js Version')}</InputLabel>
|
||||
</Grid>
|
||||
<Grid item lg={9} md={9} sm={9} xs={12}>
|
||||
<InputLabel>{aboutData.nwjs}</InputLabel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
}
|
||||
<Grid container spacing={0}>
|
||||
<Grid item lg={3} md={3} sm={3} xs={12}>
|
||||
<InputLabel style={{fontWeight: 'bold'}}>{gettext('Browser')}</InputLabel>
|
||||
</Grid>
|
||||
<Grid item lg={9} md={9} sm={9} xs={12}>
|
||||
<InputLabel>{aboutData.browser_details}</InputLabel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container spacing={0}>
|
||||
<Grid item lg={3} md={3} sm={3} xs={12}>
|
||||
<InputLabel style={{fontWeight: 'bold'}}>{gettext('Operating System')}</InputLabel>
|
||||
</Grid>
|
||||
<Grid item lg={9} md={9} sm={9} xs={12}>
|
||||
<InputLabel>{aboutData.os_details}</InputLabel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container spacing={0}>
|
||||
<Grid item lg={3} md={3} sm={3} xs={12}>
|
||||
<InputLabel style={{fontWeight: 'bold'}}>{gettext('pgAdmin Database File')}</InputLabel>
|
||||
</Grid>
|
||||
<Grid item lg={9} md={9} sm={9} xs={12}>
|
||||
<InputLabel>{aboutData.config_db}</InputLabel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container spacing={0}>
|
||||
<Grid item lg={3} md={3} sm={3} xs={12}>
|
||||
<InputLabel style={{fontWeight: 'bold'}}>{gettext('Log File')}</InputLabel>
|
||||
</Grid>
|
||||
<Grid item lg={9} md={9} sm={9} xs={12}>
|
||||
<InputLabel>{aboutData.log_file}</InputLabel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
{ (aboutData.app_mode == 'Desktop' || (aboutData.app_mode == 'Server' && aboutData.admin)) &&
|
||||
<>
|
||||
<Box flexGrow="1" display="flex" flexDirection="column">
|
||||
<Box>
|
||||
<span style={{fontWeight: 'bold'}}>{gettext('Server Configuration')}</span>
|
||||
<DefaultButton className={classes.copyBtn} onClick={()=>{
|
||||
copyToClipboard(aboutData.settings);
|
||||
setCopyText(gettext('Copied!'));
|
||||
revertCopiedText(1500);
|
||||
}}>{copyText}</DefaultButton>
|
||||
</Box>
|
||||
<Box flexGrow="1" paddingTop="1px">
|
||||
<InputText style={{height: '100%'}} controlProps={{multiline: true}} inputStyle={{resize: 'none'}}
|
||||
value={aboutData.settings}/>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
AboutComponent.propTypes = {
|
||||
closeModal: PropTypes.func
|
||||
};
|
@ -7,85 +7,56 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
define(
|
||||
['jquery', 'alertify', 'sources/pgadmin', 'sources/gettext',
|
||||
'sources/url_for','sources/utils','pgadmin.user_management.current_user',
|
||||
],
|
||||
function($, alertify, pgAdmin, gettext, url_for, commonUtils, current_user) {
|
||||
pgAdmin = pgAdmin || window.pgAdmin || {};
|
||||
import React from 'react';
|
||||
import gettext from 'sources/gettext';
|
||||
import Notify from '../../../static/js/helpers/Notifier';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import pgBrowser from 'top/browser/static/js/browser';
|
||||
import AboutComponent from './AboutComponent';
|
||||
import current_user from 'pgadmin.user_management.current_user';
|
||||
|
||||
/* Return back, this has been called more than once */
|
||||
if (pgAdmin.About)
|
||||
class About {
|
||||
static instance;
|
||||
|
||||
static getInstance(...args) {
|
||||
if (!About.instance) {
|
||||
About.instance = new About(...args);
|
||||
}
|
||||
return About.instance;
|
||||
}
|
||||
|
||||
constructor(pgAdmin, pgBrowser) {
|
||||
this.pgAdmin = pgAdmin;
|
||||
this.pgBrowser = pgBrowser;
|
||||
}
|
||||
|
||||
init() {
|
||||
if (this.initialized)
|
||||
return;
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
pgAdmin.About = {
|
||||
about_show: function() {
|
||||
if (!alertify.aboutDialog) {
|
||||
alertify.dialog('aboutDialog', function factory() {
|
||||
return {
|
||||
main: function(title, message) {
|
||||
this.set('title', title);
|
||||
this.message = message;
|
||||
},
|
||||
setup: function() {
|
||||
return {
|
||||
buttons:[{ text: gettext('OK'), key: 27,
|
||||
className: 'btn btn-primary fa fa-lg fa-check pg-alertify-button' }],
|
||||
options: {
|
||||
modal: false,
|
||||
resizable: true,
|
||||
maximizable: true,
|
||||
pinnable: false,
|
||||
closableByDimmer: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
build: function() {
|
||||
alertify.pgDialogBuild.apply(this);
|
||||
},
|
||||
hooks:{
|
||||
onshow:function(){
|
||||
var self = this;
|
||||
var container = $(this.elements.footer).find('button:not([disabled])');
|
||||
commonUtils.findAndSetFocus(container);
|
||||
$('#copy_textarea').on('click', function(){
|
||||
//Copy the server configuration details
|
||||
let textarea = document.getElementById('about-textarea');
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
$('#copy_textarea').text('Copied');
|
||||
});
|
||||
// This is a callback function to show about dialog.
|
||||
about_show() {
|
||||
let dlgHeight = 470,
|
||||
dlgWidth = 750;
|
||||
|
||||
$(this.elements.resizeHandle).on('click', function(){
|
||||
// Set the height of the Textarea
|
||||
var height = self.elements.dialog.scrollHeight - 300;
|
||||
if (height < 0)
|
||||
height = self.elements.dialog.scrollHeight - 150;
|
||||
$('#about-textarea').css({'height':height});
|
||||
});
|
||||
},
|
||||
},
|
||||
prepare:function() {
|
||||
this.setContent(this.message);
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
if(!current_user.is_admin && pgAdmin.server_mode) {
|
||||
dlgWidth = pgAdmin.Browser.stdW.md;
|
||||
dlgHeight = 300;
|
||||
}
|
||||
|
||||
$.get(url_for('about.index'),
|
||||
function(data) {
|
||||
if(!current_user.is_admin && pgAdmin.server_mode){
|
||||
alertify.aboutDialog(
|
||||
gettext('About %s', pgAdmin.Browser.utils.app_name), data
|
||||
).resizeTo(pgAdmin.Browser.stdW.md, 300);
|
||||
}else{
|
||||
alertify.aboutDialog(
|
||||
gettext('About %s', pgAdmin.Browser.utils.app_name), data
|
||||
).resizeTo(750, 470);
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
// Render About component
|
||||
Notify.showModal(gettext('About %s', pgAdmin.Browser.utils.app_name), () => {
|
||||
return <AboutComponent />;
|
||||
}, { isFullScreen: false, isResizeable: true, showFullScreen: true,
|
||||
isFullWidth: true, dialogWidth: dlgWidth, dialogHeight: dlgHeight, minHeight: dlgHeight
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return pgAdmin.About;
|
||||
});
|
||||
pgAdmin.About = About.getInstance(pgAdmin, pgBrowser);
|
||||
|
||||
module.exports = {
|
||||
About: About,
|
||||
};
|
@ -1,47 +0,0 @@
|
||||
<div class="container-fluid enable-selection">
|
||||
<div class="row">
|
||||
<div class="col-sm-3"><strong>{{ _('Version') }}</strong></div>
|
||||
<div class="col-sm-9">{{ config.APP_VERSION }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3"><strong>{{ _('Application Mode') }}</strong></div>
|
||||
<div class="col-sm-9">{{ info.app_mode }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3"><strong>{{ _('Current User') }}</strong></div>
|
||||
<div class="col-sm-9">{{ info.current_user }}</div>
|
||||
</div>
|
||||
{% if info.nwjs %}
|
||||
<div class="row">
|
||||
<div class="col-sm-3"><strong>{{ _('NW.js Version') }}</strong></div>
|
||||
<div class="col-sm-9">{{ info.nwjs }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
<div class="col-sm-3"><strong>{{ _('Browser') }}</strong></div>
|
||||
<div class="col-sm-9">{{ info.browser_details }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3"><strong>{{ _('Operating System') }}</strong></div>
|
||||
<div class="col-sm-9">{{ info.os_details }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3"><strong>{{ _('pgAdmin Database File') }}</strong></div>
|
||||
<div class="col-sm-9">{{ info.config_db }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3"><strong>{{ _('Log File') }}</strong></div>
|
||||
<div class="col-sm-9">{{ info.log_file }}</div>
|
||||
</div>
|
||||
{%if (info.app_mode == 'Desktop') or (info.app_mode == 'Server' and info.admin)%}
|
||||
<div class="row">
|
||||
<div class="col-sm-3"><b>{{ _('Server Configuration') }}</b></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-9">
|
||||
<textarea id="about-textarea" rows="7" cols="150" style="position: relative;width:105%; white-space: pre-wrap;" readonly>{{ info.settings|safe }}</textarea>
|
||||
<button class="btn btn-secondary about-copy" id="copy_textarea" style="position:absolute;top:0;right:0;z-index:20;">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
@ -122,6 +122,8 @@ basicSettings = createMuiTheme(basicSettings, {
|
||||
inputMultiline: {
|
||||
padding: basicSettings.spacing(0.75, 1.5),
|
||||
resize: 'vertical',
|
||||
height: '100%',
|
||||
boxSizing: 'border-box',
|
||||
},
|
||||
adornedEnd: {
|
||||
paddingRight: basicSettings.spacing(0.75),
|
||||
|
@ -326,7 +326,7 @@ FormInputDateTimePicker.propTypes = {
|
||||
|
||||
/* Use forwardRef to pass ref prop to OutlinedInput */
|
||||
export const InputText = forwardRef(({
|
||||
cid, helpid, readonly, disabled, value, onChange, controlProps, type, size, ...props }, ref) => {
|
||||
cid, helpid, readonly, disabled, value, onChange, controlProps, type, size, inputStyle, ...props }, ref) => {
|
||||
|
||||
const maxlength = typeof(controlProps?.maxLength) != 'undefined' ? controlProps.maxLength : 255;
|
||||
|
||||
@ -370,7 +370,8 @@ export const InputText = forwardRef(({
|
||||
id: cid,
|
||||
maxLength: controlProps?.multiline ? null : maxlength,
|
||||
'aria-describedby': helpid,
|
||||
...(type ? { pattern: !_.isUndefined(controlProps) && !_.isUndefined(controlProps.pattern) ? controlProps.pattern : patterns[type] } : {})
|
||||
...(type ? { pattern: !_.isUndefined(controlProps) && !_.isUndefined(controlProps.pattern) ? controlProps.pattern : patterns[type] } : {}),
|
||||
style: inputStyle || {}
|
||||
}}
|
||||
readOnly={Boolean(readonly)}
|
||||
disabled={Boolean(disabled)}
|
||||
@ -399,6 +400,7 @@ InputText.propTypes = {
|
||||
controlProps: PropTypes.object,
|
||||
type: PropTypes.string,
|
||||
size: PropTypes.string,
|
||||
inputStyle: PropTypes.object
|
||||
};
|
||||
|
||||
export function FormInputText({ hasError, required, label, className, helpMessage, testcid, ...props }) {
|
||||
|
@ -155,7 +155,7 @@ const dialogStyle = makeStyles((theme) => ({
|
||||
}
|
||||
}));
|
||||
|
||||
function PaperComponent(props) {
|
||||
function PaperComponent({minHeight, minWidth, ...props}) {
|
||||
let classes = dialogStyle();
|
||||
let [dialogPosition, setDialogPosition] = useState(null);
|
||||
let resizeable = props.isresizeable == 'true' ? true : false;
|
||||
@ -182,8 +182,10 @@ function PaperComponent(props) {
|
||||
...(props.width && { width: props.width }),
|
||||
...(props.height && { height: props.height }),
|
||||
}}
|
||||
{...(props.width && { minWidth: MIN_WIDTH })}
|
||||
{...(props.height && { minHeight: MIN_HEIGHT })}
|
||||
minWidth = { minWidth || MIN_WIDTH }
|
||||
minHeight = { minHeight || MIN_HEIGHT }
|
||||
// {...(props.width && { minWidth: MIN_WIDTH })}
|
||||
// {...(props.height && { minHeight: MIN_HEIGHT })}
|
||||
bounds="window"
|
||||
enableResizing={setEnableResizing()}
|
||||
position={setConditionalPosition()}
|
||||
@ -215,6 +217,8 @@ PaperComponent.propTypes = {
|
||||
isresizeable: PropTypes.string,
|
||||
width: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
minWidth: PropTypes.number,
|
||||
minHeight: PropTypes.number,
|
||||
};
|
||||
|
||||
export const useModalStyles = makeStyles((theme) => ({
|
||||
@ -254,7 +258,7 @@ export const useModalStyles = makeStyles((theme) => ({
|
||||
}
|
||||
}));
|
||||
|
||||
function ModalContainer({ id, title, content, dialogHeight, dialogWidth, onClose, fullScreen = false, isFullWidth = false, showFullScreen = false, isResizeable = false }) {
|
||||
function ModalContainer({ id, title, content, dialogHeight, dialogWidth, onClose, fullScreen = false, isFullWidth = false, showFullScreen = false, isResizeable = false, minHeight = MIN_HEIGHT, minWidth = MIN_WIDTH }) {
|
||||
let useModalRef = useModal();
|
||||
const classes = useModalStyles();
|
||||
let closeModal = (_e, reason) => {
|
||||
@ -270,7 +274,7 @@ function ModalContainer({ id, title, content, dialogHeight, dialogWidth, onClose
|
||||
open={true}
|
||||
onClose={closeModal}
|
||||
PaperComponent={PaperComponent}
|
||||
PaperProps={{ 'isfullscreen': isfullScreen.toString(), 'isresizeable': isResizeable.toString(), width: dialogWidth, height: dialogHeight }}
|
||||
PaperProps={{ 'isfullscreen': isfullScreen.toString(), 'isresizeable': isResizeable.toString(), width: dialogWidth, height: dialogHeight, minHeight: minHeight, minWidth: minWidth }}
|
||||
fullScreen={isfullScreen}
|
||||
fullWidth={isFullWidth}
|
||||
disableBackdropClick
|
||||
@ -309,4 +313,6 @@ ModalContainer.propTypes = {
|
||||
dialogHeight: PropTypes.number,
|
||||
dialogWidth: PropTypes.number,
|
||||
onClose: PropTypes.func,
|
||||
minWidth: PropTypes.number,
|
||||
minHeight: PropTypes.number,
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user