use correct event handler name convention. register directive on startup
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { react2AngularDirective } from 'app/core/utils/react2angular';
|
import { react2AngularDirective } from 'app/core/utils/react2angular';
|
||||||
|
import { QueryEditor } from 'app/plugins/datasource/stackdriver/components/QueryEditor';
|
||||||
import { PasswordStrength } from './components/PasswordStrength';
|
import { PasswordStrength } from './components/PasswordStrength';
|
||||||
import PageHeader from './components/PageHeader/PageHeader';
|
import PageHeader from './components/PageHeader/PageHeader';
|
||||||
import EmptyListCTA from './components/EmptyListCTA/EmptyListCTA';
|
import EmptyListCTA from './components/EmptyListCTA/EmptyListCTA';
|
||||||
@@ -29,4 +30,11 @@ export function registerAngularDirectives() {
|
|||||||
'placeholder',
|
'placeholder',
|
||||||
['variables', { watchDepth: 'reference' }],
|
['variables', { watchDepth: 'reference' }],
|
||||||
]);
|
]);
|
||||||
|
react2AngularDirective('queryEditor', QueryEditor, [
|
||||||
|
'target',
|
||||||
|
'onQueryChange',
|
||||||
|
'onExecuteQuery',
|
||||||
|
['events', { watchDepth: 'reference' }],
|
||||||
|
['datasource', { watchDepth: 'reference' }],
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export class StackdriverAnnotationsQueryCtrl {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.annotation.target = this.annotation.target || {};
|
this.annotation.target = this.annotation.target || {};
|
||||||
this.handleQueryChange = this.handleQueryChange.bind(this);
|
this.onQueryChange = this.onQueryChange.bind(this);
|
||||||
|
|
||||||
react2AngularDirective('annotationQueryEditor', AnnotationQueryEditor, [
|
react2AngularDirective('annotationQueryEditor', AnnotationQueryEditor, [
|
||||||
'target',
|
'target',
|
||||||
@@ -17,7 +17,7 @@ export class StackdriverAnnotationsQueryCtrl {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleQueryChange(target) {
|
onQueryChange(target) {
|
||||||
Object.assign(this.annotation.target, target);
|
Object.assign(this.annotation.target, target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export class Aggregations extends React.Component<Props, State> {
|
|||||||
this.setState({ aggOptions });
|
this.setState({ aggOptions });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleToggleDisplayAdvanced() {
|
onToggleDisplayAdvanced() {
|
||||||
this.setState(state => ({
|
this.setState(state => ({
|
||||||
displayAdvancedOptions: !state.displayAdvancedOptions,
|
displayAdvancedOptions: !state.displayAdvancedOptions,
|
||||||
}));
|
}));
|
||||||
@@ -78,7 +78,7 @@ export class Aggregations extends React.Component<Props, State> {
|
|||||||
</div>
|
</div>
|
||||||
<div className="gf-form gf-form--grow">
|
<div className="gf-form gf-form--grow">
|
||||||
<label className="gf-form-label gf-form-label--grow">
|
<label className="gf-form-label gf-form-label--grow">
|
||||||
<a onClick={() => this.handleToggleDisplayAdvanced()}>
|
<a onClick={() => this.onToggleDisplayAdvanced()}>
|
||||||
<>
|
<>
|
||||||
<i className={`fa fa-caret-${displayAdvancedOptions ? 'down' : 'right'}`} /> Advanced Options
|
<i className={`fa fa-caret-${displayAdvancedOptions ? 'down' : 'right'}`} /> Advanced Options
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ export interface State {
|
|||||||
export class AliasBy extends Component<Props, State> {
|
export class AliasBy extends Component<Props, State> {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.handleChange = this.handleChange.bind(this);
|
this.updateQuery = debounce(this.updateQuery.bind(this), 500);
|
||||||
this.onChange = debounce(this.onChange.bind(this), 500);
|
|
||||||
this.state = { value: '' };
|
this.state = { value: '' };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,18 +27,18 @@ export class AliasBy extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange(e) {
|
onChange(e) {
|
||||||
this.setState({ value: e.target.value });
|
this.setState({ value: e.target.value });
|
||||||
this.onChange(e.target.value);
|
this.updateQuery(e.target.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange(value) {
|
updateQuery(value) {
|
||||||
this.props.onChange(value);
|
this.props.onChange(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<>
|
<React.Fragment>
|
||||||
<div className="gf-form-inline">
|
<div className="gf-form-inline">
|
||||||
<div className="gf-form">
|
<div className="gf-form">
|
||||||
<label className="gf-form-label query-keyword width-9">Alias By</label>
|
<label className="gf-form-label query-keyword width-9">Alias By</label>
|
||||||
@@ -47,14 +46,14 @@ export class AliasBy extends Component<Props, State> {
|
|||||||
type="text"
|
type="text"
|
||||||
className="gf-form-input width-24"
|
className="gf-form-input width-24"
|
||||||
value={this.state.value}
|
value={this.state.value}
|
||||||
onChange={this.handleChange}
|
onChange={e => this.onChange(e)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="gf-form gf-form--grow">
|
<div className="gf-form gf-form--grow">
|
||||||
<div className="gf-form-label gf-form-label--grow" />
|
<div className="gf-form-label gf-form-label--grow" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export class AnnotationQueryEditor extends React.Component<Props, State> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMetricTypeChange({ valueType, metricKind, type, unit }) {
|
onMetricTypeChange({ valueType, metricKind, type, unit }) {
|
||||||
const { onQueryChange } = this.props;
|
const { onQueryChange } = this.props;
|
||||||
this.setState(
|
this.setState(
|
||||||
{
|
{
|
||||||
@@ -51,7 +51,7 @@ export class AnnotationQueryEditor extends React.Component<Props, State> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange(prop, value) {
|
onChange(prop, value) {
|
||||||
this.setState({ [prop]: value }, () => {
|
this.setState({ [prop]: value }, () => {
|
||||||
this.props.onQueryChange(this.state);
|
this.props.onQueryChange(this.state);
|
||||||
});
|
});
|
||||||
@@ -68,12 +68,12 @@ export class AnnotationQueryEditor extends React.Component<Props, State> {
|
|||||||
metricType={metricType}
|
metricType={metricType}
|
||||||
templateSrv={datasource.templateSrv}
|
templateSrv={datasource.templateSrv}
|
||||||
datasource={datasource}
|
datasource={datasource}
|
||||||
onChange={value => this.handleMetricTypeChange(value)}
|
onChange={value => this.onMetricTypeChange(value)}
|
||||||
>
|
>
|
||||||
{metric => (
|
{metric => (
|
||||||
<>
|
<>
|
||||||
<Filter
|
<Filter
|
||||||
filtersChanged={value => this.handleChange('filters', value)}
|
filtersChanged={value => this.onChange('filters', value)}
|
||||||
filters={filters}
|
filters={filters}
|
||||||
refId={refId}
|
refId={refId}
|
||||||
hideGroupBys={true}
|
hideGroupBys={true}
|
||||||
@@ -91,7 +91,7 @@ export class AnnotationQueryEditor extends React.Component<Props, State> {
|
|||||||
type="text"
|
type="text"
|
||||||
className="gf-form-input width-20"
|
className="gf-form-input width-20"
|
||||||
value={title}
|
value={title}
|
||||||
onChange={e => this.handleChange('title', e.target.value)}
|
onChange={e => this.onChange('title', e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="gf-form">
|
<div className="gf-form">
|
||||||
@@ -100,7 +100,7 @@ export class AnnotationQueryEditor extends React.Component<Props, State> {
|
|||||||
type="text"
|
type="text"
|
||||||
className="gf-form-input width-20"
|
className="gf-form-input width-20"
|
||||||
value={text}
|
value={text}
|
||||||
onChange={e => this.handleChange('text', e.target.value)}
|
onChange={e => this.onChange('text', e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="gf-form gf-form--grow">
|
<div className="gf-form gf-form--grow">
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ export class Help extends React.Component<Props, State> {
|
|||||||
displaRawQuery: false,
|
displaRawQuery: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
handleHelpClicked() {
|
onHelpClicked() {
|
||||||
this.setState({ displayHelp: !this.state.displayHelp });
|
this.setState({ displayHelp: !this.state.displayHelp });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRawQueryClicked() {
|
onRawQueryClicked() {
|
||||||
this.setState({ displaRawQuery: !this.state.displaRawQuery });
|
this.setState({ displaRawQuery: !this.state.displaRawQuery });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ export class Help extends React.Component<Props, State> {
|
|||||||
<>
|
<>
|
||||||
<div className="gf-form-inline">
|
<div className="gf-form-inline">
|
||||||
<Project datasource={datasource} />
|
<Project datasource={datasource} />
|
||||||
<div className="gf-form" onClick={() => this.handleHelpClicked()}>
|
<div className="gf-form" onClick={() => this.onHelpClicked()}>
|
||||||
<label className="gf-form-label query-keyword">
|
<label className="gf-form-label query-keyword">
|
||||||
Show Help
|
Show Help
|
||||||
<i className={`fa fa-caret-${displayHelp ? 'down' : 'right'}`} />
|
<i className={`fa fa-caret-${displayHelp ? 'down' : 'right'}`} />
|
||||||
@@ -46,7 +46,7 @@ export class Help extends React.Component<Props, State> {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{rawQuery && (
|
{rawQuery && (
|
||||||
<div className="gf-form" onClick={() => this.handleRawQueryClicked()}>
|
<div className="gf-form" onClick={() => this.onRawQueryClicked()}>
|
||||||
<label className="gf-form-label query-keyword">
|
<label className="gf-form-label query-keyword">
|
||||||
Raw Query
|
Raw Query
|
||||||
<i className={`fa fa-caret-${displaRawQuery ? 'down' : 'right'}`} ng-show="ctrl.showHelp" />
|
<i className={`fa fa-caret-${displaRawQuery ? 'down' : 'right'}`} ng-show="ctrl.showHelp" />
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export class Metrics extends React.Component<Props, State> {
|
|||||||
return metricsByService;
|
return metricsByService;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleServiceChange(service) {
|
onServiceChange(service) {
|
||||||
const { metricDescriptors } = this.state;
|
const { metricDescriptors } = this.state;
|
||||||
const { templateSrv, metricType } = this.props;
|
const { templateSrv, metricType } = this.props;
|
||||||
|
|
||||||
@@ -112,11 +112,11 @@ export class Metrics extends React.Component<Props, State> {
|
|||||||
this.setState({ service, metrics });
|
this.setState({ service, metrics });
|
||||||
|
|
||||||
if (metrics.length > 0 && !metrics.some(m => m.value === templateSrv.replace(metricType))) {
|
if (metrics.length > 0 && !metrics.some(m => m.value === templateSrv.replace(metricType))) {
|
||||||
this.handleMetricTypeChange(metrics[0].value);
|
this.onMetricTypeChange(metrics[0].value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMetricTypeChange(value) {
|
onMetricTypeChange(value) {
|
||||||
const metricDescriptor = this.getSelectedMetricDescriptor(value);
|
const metricDescriptor = this.getSelectedMetricDescriptor(value);
|
||||||
this.setState({ metricDescriptor });
|
this.setState({ metricDescriptor });
|
||||||
this.props.onChange({ ...metricDescriptor, type: value });
|
this.props.onChange({ ...metricDescriptor, type: value });
|
||||||
@@ -151,7 +151,7 @@ export class Metrics extends React.Component<Props, State> {
|
|||||||
<div className="gf-form">
|
<div className="gf-form">
|
||||||
<span className="gf-form-label width-9 query-keyword">Service</span>
|
<span className="gf-form-label width-9 query-keyword">Service</span>
|
||||||
<MetricSelect
|
<MetricSelect
|
||||||
onChange={value => this.handleServiceChange(value)}
|
onChange={value => this.onServiceChange(value)}
|
||||||
value={service}
|
value={service}
|
||||||
options={services}
|
options={services}
|
||||||
isSearchable={false}
|
isSearchable={false}
|
||||||
@@ -167,7 +167,7 @@ export class Metrics extends React.Component<Props, State> {
|
|||||||
<div className="gf-form">
|
<div className="gf-form">
|
||||||
<span className="gf-form-label width-9 query-keyword">Metric</span>
|
<span className="gf-form-label width-9 query-keyword">Metric</span>
|
||||||
<MetricSelect
|
<MetricSelect
|
||||||
onChange={value => this.handleMetricTypeChange(value)}
|
onChange={value => this.onMetricTypeChange(value)}
|
||||||
value={metricType}
|
value={metricType}
|
||||||
variables={templateSrv.variables}
|
variables={templateSrv.variables}
|
||||||
options={[
|
options={[
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export class QueryEditor extends React.Component<Props, State> {
|
|||||||
this.setState({ lastQuery, lastQueryError });
|
this.setState({ lastQuery, lastQueryError });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMetricTypeChange({ valueType, metricKind, type, unit }) {
|
onMetricTypeChange({ valueType, metricKind, type, unit }) {
|
||||||
const { datasource, onQueryChange, onExecuteQuery } = this.props;
|
const { datasource, onQueryChange, onExecuteQuery } = this.props;
|
||||||
const { perSeriesAligner, alignOptions } = getAlignmentPickerData(
|
const { perSeriesAligner, alignOptions } = getAlignmentPickerData(
|
||||||
{ valueType, metricKind, perSeriesAligner: this.state.perSeriesAligner },
|
{ valueType, metricKind, perSeriesAligner: this.state.perSeriesAligner },
|
||||||
@@ -113,7 +113,7 @@ export class QueryEditor extends React.Component<Props, State> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange(prop, value) {
|
onPropertyChange(prop, value) {
|
||||||
this.setState({ [prop]: value }, () => {
|
this.setState({ [prop]: value }, () => {
|
||||||
this.props.onQueryChange(this.state);
|
this.props.onQueryChange(this.state);
|
||||||
this.props.onExecuteQuery();
|
this.props.onExecuteQuery();
|
||||||
@@ -144,13 +144,13 @@ export class QueryEditor extends React.Component<Props, State> {
|
|||||||
metricType={metricType}
|
metricType={metricType}
|
||||||
templateSrv={datasource.templateSrv}
|
templateSrv={datasource.templateSrv}
|
||||||
datasource={datasource}
|
datasource={datasource}
|
||||||
onChange={value => this.handleMetricTypeChange(value)}
|
onChange={value => this.onMetricTypeChange(value)}
|
||||||
>
|
>
|
||||||
{metric => (
|
{metric => (
|
||||||
<>
|
<>
|
||||||
<Filter
|
<Filter
|
||||||
filtersChanged={value => this.handleChange('filters', value)}
|
filtersChanged={value => this.onPropertyChange('filters', value)}
|
||||||
groupBysChanged={value => this.handleChange('groupBys', value)}
|
groupBysChanged={value => this.onPropertyChange('groupBys', value)}
|
||||||
filters={filters}
|
filters={filters}
|
||||||
groupBys={groupBys}
|
groupBys={groupBys}
|
||||||
refId={refId}
|
refId={refId}
|
||||||
@@ -164,7 +164,7 @@ export class QueryEditor extends React.Component<Props, State> {
|
|||||||
templateSrv={datasource.templateSrv}
|
templateSrv={datasource.templateSrv}
|
||||||
crossSeriesReducer={crossSeriesReducer}
|
crossSeriesReducer={crossSeriesReducer}
|
||||||
groupBys={groupBys}
|
groupBys={groupBys}
|
||||||
onChange={value => this.handleChange('crossSeriesReducer', value)}
|
onChange={value => this.onPropertyChange('crossSeriesReducer', value)}
|
||||||
>
|
>
|
||||||
{displayAdvancedOptions =>
|
{displayAdvancedOptions =>
|
||||||
displayAdvancedOptions && (
|
displayAdvancedOptions && (
|
||||||
@@ -172,16 +172,16 @@ export class QueryEditor extends React.Component<Props, State> {
|
|||||||
alignOptions={alignOptions}
|
alignOptions={alignOptions}
|
||||||
templateSrv={datasource.templateSrv}
|
templateSrv={datasource.templateSrv}
|
||||||
perSeriesAligner={perSeriesAligner}
|
perSeriesAligner={perSeriesAligner}
|
||||||
onChange={value => this.handleChange('perSeriesAligner', value)}
|
onChange={value => this.onPropertyChange('perSeriesAligner', value)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</Aggregations>
|
</Aggregations>
|
||||||
<AliasBy value={aliasBy} onChange={value => this.handleChange('aliasBy', value)} />
|
<AliasBy value={aliasBy} onChange={value => this.onPropertyChange('aliasBy', value)} />
|
||||||
<AlignmentPeriods
|
<AlignmentPeriods
|
||||||
templateSrv={datasource.templateSrv}
|
templateSrv={datasource.templateSrv}
|
||||||
alignmentPeriod={alignmentPeriod}
|
alignmentPeriod={alignmentPeriod}
|
||||||
onChange={value => this.handleChange('alignmentPeriod', value)}
|
onChange={value => this.onPropertyChange('alignmentPeriod', value)}
|
||||||
/>
|
/>
|
||||||
<Help datasource={datasource} rawQuery={lastQuery} lastQueryError={lastQueryError} />
|
<Help datasource={datasource} rawQuery={lastQuery} lastQueryError={lastQueryError} />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export class StackdriverVariableQueryEditor extends PureComponent<VariableQueryP
|
|||||||
this.setState(state);
|
this.setState(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleQueryTypeChange(event) {
|
async onQueryTypeChange(event) {
|
||||||
const state: any = {
|
const state: any = {
|
||||||
selectedQueryType: event.target.value,
|
selectedQueryType: event.target.value,
|
||||||
...await this.getLabels(this.state.selectedMetricType, event.target.value),
|
...await this.getLabels(this.state.selectedMetricType, event.target.value),
|
||||||
@@ -186,7 +186,7 @@ export class StackdriverVariableQueryEditor extends PureComponent<VariableQueryP
|
|||||||
<SimpleSelect
|
<SimpleSelect
|
||||||
value={this.state.selectedQueryType}
|
value={this.state.selectedQueryType}
|
||||||
options={this.queryTypes}
|
options={this.queryTypes}
|
||||||
onValueChange={e => this.handleQueryTypeChange(e)}
|
onValueChange={e => this.onQueryTypeChange(e)}
|
||||||
label="Query Type"
|
label="Query Type"
|
||||||
/>
|
/>
|
||||||
{this.renderQueryTypeSwitch(this.state.selectedQueryType)}
|
{this.renderQueryTypeSwitch(this.state.selectedQueryType)}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<annotation-query-editor
|
<annotation-query-editor
|
||||||
target="ctrl.annotation.target"
|
target="ctrl.annotation.target"
|
||||||
on-query-change="(ctrl.handleQueryChange)"
|
on-query-change="(ctrl.onQueryChange)"
|
||||||
datasource="ctrl.datasource"
|
datasource="ctrl.datasource"
|
||||||
></annotation-query-editor>
|
></annotation-query-editor>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
target="ctrl.target"
|
target="ctrl.target"
|
||||||
events="ctrl.panelCtrl.events"
|
events="ctrl.panelCtrl.events"
|
||||||
datasource="ctrl.datasource"
|
datasource="ctrl.datasource"
|
||||||
on-query-change="(ctrl.handleQueryChange)"
|
on-query-change="(ctrl.onQueryChange)"
|
||||||
on-execute-query="(ctrl.handleExecuteQuery)"
|
on-execute-query="(ctrl.onExecuteQuery)"
|
||||||
></query-editor>
|
></query-editor>
|
||||||
</query-editor-row>
|
</query-editor-row>
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { QueryCtrl } from 'app/plugins/sdk';
|
import { QueryCtrl } from 'app/plugins/sdk';
|
||||||
import { react2AngularDirective } from 'app/core/utils/react2angular';
|
|
||||||
|
|
||||||
import { QueryEditor } from './components/QueryEditor';
|
|
||||||
import { Target } from './types';
|
import { Target } from './types';
|
||||||
|
|
||||||
export class StackdriverQueryCtrl extends QueryCtrl {
|
export class StackdriverQueryCtrl extends QueryCtrl {
|
||||||
@@ -12,22 +9,15 @@ export class StackdriverQueryCtrl extends QueryCtrl {
|
|||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($scope, $injector) {
|
constructor($scope, $injector) {
|
||||||
super($scope, $injector);
|
super($scope, $injector);
|
||||||
this.handleQueryChange = this.handleQueryChange.bind(this);
|
this.onQueryChange = this.onQueryChange.bind(this);
|
||||||
this.handleExecuteQuery = this.handleExecuteQuery.bind(this);
|
this.onExecuteQuery = this.onExecuteQuery.bind(this);
|
||||||
react2AngularDirective('queryEditor', QueryEditor, [
|
|
||||||
'target',
|
|
||||||
'onQueryChange',
|
|
||||||
'onExecuteQuery',
|
|
||||||
['events', { watchDepth: 'reference' }],
|
|
||||||
['datasource', { watchDepth: 'reference' }],
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleQueryChange(target: Target) {
|
onQueryChange(target: Target) {
|
||||||
Object.assign(this.target, target);
|
Object.assign(this.target, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleExecuteQuery() {
|
onExecuteQuery() {
|
||||||
this.$scope.ctrl.refresh();
|
this.$scope.ctrl.refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user