Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
N
nexpie-grafana-theme
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Registry
Registry
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kornkitt Poolsup
nexpie-grafana-theme
Commits
8bb9d92a
Commit
8bb9d92a
authored
Oct 12, 2017
by
Torkel Ödegaard
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
grid: minor progress on panel repeats
parent
215d5986
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
144 additions
and
264 deletions
+144
-264
public/app/core/core.ts
+2
-0
public/app/features/dashboard/dashboard_ctrl.ts
+3
-5
public/app/features/dashboard/dashboard_model.ts
+89
-24
public/app/features/dashboard/dashgrid/DashboardGrid.tsx
+1
-0
public/app/features/dashboard/dynamic_dashboard_srv.ts
+0
-192
public/app/features/dashboard/export/exporter.ts
+4
-9
public/app/features/dashboard/panel_model.ts
+9
-1
public/app/features/dashboard/specs/dynamic_dashboard_srv_specs.ts
+0
-0
public/app/features/dashboard/specs/exporter_specs.ts
+3
-29
public/app/features/dashboard/unsaved_changes_modal.ts
+2
-2
public/app/features/panel/panel_ctrl.ts
+25
-2
public/app/partials/panelgeneral.html
+6
-0
No files found.
public/app/core/core.ts
View file @
8bb9d92a
...
...
@@ -51,8 +51,10 @@ import {userGroupPicker} from './components/user_group_picker';
import
{
geminiScrollbar
}
from
'./components/scroll/scroll'
;
import
{
gfPageDirective
}
from
'./components/gf_page'
;
import
{
orgSwitcher
}
from
'./components/org_switcher'
;
import
{
profiler
}
from
'./profiler'
;
export
{
profiler
,
arrayJoin
,
coreModule
,
grafanaAppDirective
,
...
...
public/app/features/dashboard/dashboard_ctrl.ts
View file @
8bb9d92a
...
...
@@ -20,7 +20,6 @@ export class DashboardCtrl implements PanelContainer {
private
alertingSrv
,
private
dashboardSrv
,
private
unsavedChangesSrv
,
private
dynamicDashboardSrv
,
private
dashboardViewStateSrv
,
private
panelLoader
)
{
// temp hack due to way dashboards are loaded
...
...
@@ -57,10 +56,9 @@ export class DashboardCtrl implements PanelContainer {
.
catch
(
this
.
onInitFailed
.
bind
(
this
,
'Templating init failed'
,
false
))
// continue
.
finally
(()
=>
{
this
.
dashboard
=
dashboard
;
this
.
d
ynamicDashboardSrv
.
init
(
dashboard
)
;
this
.
d
ynamicDashboardSrv
.
proces
s
();
this
.
d
ashboard
=
dashboard
;
this
.
d
ashboard
.
processRepeat
s
();
this
.
unsavedChangesSrv
.
init
(
dashboard
,
this
.
$scope
);
...
...
@@ -97,7 +95,7 @@ export class DashboardCtrl implements PanelContainer {
}
templateVariableUpdated
()
{
this
.
d
ynamicDashboardSrv
.
proces
s
();
this
.
d
ashboard
.
processRepeat
s
();
}
setWindowTitleAndTheme
()
{
...
...
public/app/features/dashboard/dashboard_model.ts
View file @
8bb9d92a
...
...
@@ -2,7 +2,7 @@ import moment from 'moment';
import
_
from
'lodash'
;
import
{
DEFAULT_ANNOTATION_COLOR
}
from
'app/core/utils/colors'
;
import
{
Emitter
,
contextSrv
,
appEvents
}
from
'app/core/core'
;
import
{
Emitter
,
contextSrv
}
from
'app/core/core'
;
import
{
DashboardRow
}
from
'./row/row_model'
;
import
{
PanelModel
}
from
'./panel_model'
;
import
sortByKeys
from
'app/core/utils/sort_by_keys'
;
...
...
@@ -34,12 +34,19 @@ export class DashboardModel {
revision
:
number
;
links
:
any
;
gnetId
:
any
;
meta
:
any
;
events
:
any
;
editMode
:
boolean
;
folderId
:
number
;
panels
:
PanelModel
[];
// ------------------
// not persisted
// ------------------
// repeat process cycles
iteration
:
number
;
meta
:
any
;
events
:
Emitter
;
static
nonPersistedProperties
:
{[
str
:
string
]:
boolean
}
=
{
"events"
:
true
,
"meta"
:
true
,
...
...
@@ -193,7 +200,12 @@ export class DashboardModel {
this
.
panels
.
unshift
(
new
PanelModel
(
panel
));
// make sure it's sorted by pos
this
.
sortPanelsByGridPos
();
this
.
events
.
emit
(
'panel-added'
,
panel
);
}
private
sortPanelsByGridPos
()
{
this
.
panels
.
sort
(
function
(
panelA
,
panelB
)
{
if
(
panelA
.
gridPos
.
y
===
panelB
.
gridPos
.
y
)
{
return
panelA
.
gridPos
.
x
-
panelB
.
gridPos
.
x
;
...
...
@@ -201,33 +213,86 @@ export class DashboardModel {
return
panelA
.
gridPos
.
y
-
panelB
.
gridPos
.
y
;
}
});
}
this
.
events
.
emit
(
'panel-added'
,
panel
);
cleanUpRepeats
()
{
this
.
processRepeats
(
true
);
}
removePanel
(
panel
,
ask
?)
{
// confirm deletion
if
(
ask
!==
false
)
{
var
text2
,
confirmText
;
if
(
panel
.
alert
)
{
text2
=
"Panel includes an alert rule, removing panel will also remove alert rule"
;
confirmText
=
"YES"
;
}
processRepeats
(
cleanUpOnly
?:
boolean
)
{
if
(
this
.
snapshot
||
this
.
templating
.
list
.
length
===
0
)
{
return
;
}
this
.
iteration
=
(
this
.
iteration
||
new
Date
().
getTime
())
+
1
;
appEvents
.
emit
(
'confirm-modal'
,
{
title
:
'Remove Panel'
,
text
:
'Are you sure you want to remove this panel?'
,
text2
:
text2
,
icon
:
'fa-trash'
,
confirmText
:
confirmText
,
yesText
:
'Remove'
,
onConfirm
:
()
=>
{
this
.
removePanel
(
panel
,
false
);
let
panelsToRemove
=
[];
for
(
let
panel
of
this
.
panels
)
{
if
(
panel
.
repeat
)
{
if
(
!
cleanUpOnly
)
{
this
.
repeatPanel
(
panel
);
}
});
return
;
}
else
if
(
panel
.
repeatPanelId
&&
panel
.
repeatIteration
!==
this
.
iteration
)
{
panelsToRemove
.
push
(
panel
);
}
}
// remove panels
_
.
pull
(
this
.
panels
,
...
panelsToRemove
);
this
.
sortPanelsByGridPos
();
this
.
events
.
emit
(
'repeats-processed'
);
}
getRepeatClone
(
sourcePanel
,
index
)
{
// if first clone return source
if
(
index
===
0
)
{
return
sourcePanel
;
}
var
clone
=
new
PanelModel
(
sourcePanel
.
getSaveModel
());
clone
.
id
=
this
.
getNextPanelId
();
this
.
panels
.
push
(
clone
);
clone
.
repeatIteration
=
this
.
iteration
;
clone
.
repeatPanelId
=
sourcePanel
.
id
;
clone
.
repeat
=
null
;
return
clone
;
}
repeatPanel
(
panel
:
PanelModel
)
{
var
variable
=
_
.
find
(
this
.
templating
.
list
,
{
name
:
panel
.
repeat
});
if
(
!
variable
)
{
return
;
}
var
selected
;
if
(
variable
.
current
.
text
===
'All'
)
{
selected
=
variable
.
options
.
slice
(
1
,
variable
.
options
.
length
);
}
else
{
selected
=
_
.
filter
(
variable
.
options
,
{
selected
:
true
});
}
for
(
let
index
=
0
;
index
<
selected
.
length
;
index
++
)
{
var
option
=
selected
[
index
];
var
copy
=
this
.
getRepeatClone
(
panel
,
index
);
copy
.
scopedVars
=
copy
.
scopedVars
||
{};
copy
.
scopedVars
[
variable
.
name
]
=
option
;
// souce panel uses original possition
if
(
index
===
0
)
{
continue
;
}
if
(
panel
.
repeatDirection
===
'Y'
)
{
copy
.
gridPos
.
y
=
panel
.
gridPos
.
y
+
(
panel
.
gridPos
.
h
*
index
);
}
else
{
copy
.
gridPos
.
x
=
panel
.
gridPos
.
x
+
(
panel
.
gridPos
.
w
*
index
);
}
}
}
removePanel
(
panel
:
PanelModel
)
{
var
index
=
_
.
indexOf
(
this
.
panels
,
panel
);
this
.
panels
.
splice
(
index
,
1
);
this
.
events
.
emit
(
'panel-removed'
,
panel
);
...
...
public/app/features/dashboard/dashgrid/DashboardGrid.tsx
View file @
8bb9d92a
...
...
@@ -69,6 +69,7 @@ export class DashboardGrid extends React.Component<DashboardGridProps, any> {
this
.
dashboard
=
this
.
panelContainer
.
getDashboard
();
this
.
dashboard
.
on
(
'panel-added'
,
this
.
triggerForceUpdate
.
bind
(
this
));
this
.
dashboard
.
on
(
'panel-removed'
,
this
.
triggerForceUpdate
.
bind
(
this
));
this
.
dashboard
.
on
(
'repeats-processed'
,
this
.
triggerForceUpdate
.
bind
(
this
));
this
.
dashboard
.
on
(
'view-mode-changed'
,
this
.
triggerForceUpdate
.
bind
(
this
));
}
...
...
public/app/features/dashboard/dynamic_dashboard_srv.ts
deleted
100644 → 0
View file @
215d5986
import
angular
from
'angular'
;
import
_
from
'lodash'
;
import
coreModule
from
'app/core/core_module'
;
import
{
DashboardRow
}
from
'./row/row_model'
;
export
class
DynamicDashboardSrv
{
iteration
:
number
;
dashboard
:
any
;
variables
:
any
;
init
(
dashboard
)
{
this
.
dashboard
=
dashboard
;
this
.
variables
=
dashboard
.
templating
.
list
;
}
process
(
options
?)
{
if
(
this
.
dashboard
.
snapshot
||
this
.
variables
.
length
===
0
)
{
return
;
}
this
.
iteration
=
(
this
.
iteration
||
new
Date
().
getTime
())
+
1
;
options
=
options
||
{};
var
cleanUpOnly
=
options
.
cleanUpOnly
;
var
i
,
j
,
row
,
panel
;
if
(
this
.
dashboard
.
rows
)
{
// cleanup scopedVars
for
(
i
=
0
;
i
<
this
.
dashboard
.
rows
.
length
;
i
++
)
{
row
=
this
.
dashboard
.
rows
[
i
];
delete
row
.
scopedVars
;
for
(
j
=
0
;
j
<
row
.
panels
.
length
;
j
++
)
{
delete
row
.
panels
[
j
].
scopedVars
;
}
}
for
(
i
=
0
;
i
<
this
.
dashboard
.
rows
.
length
;
i
++
)
{
row
=
this
.
dashboard
.
rows
[
i
];
// handle row repeats
if
(
row
.
repeat
)
{
if
(
!
cleanUpOnly
)
{
this
.
repeatRow
(
row
,
i
);
}
}
else
if
(
row
.
repeatRowId
&&
row
.
repeatIteration
!==
this
.
iteration
)
{
// clean up old left overs
this
.
dashboard
.
removeRow
(
row
,
true
);
i
=
i
-
1
;
continue
;
}
// repeat panels
for
(
j
=
0
;
j
<
row
.
panels
.
length
;
j
++
)
{
panel
=
row
.
panels
[
j
];
if
(
panel
.
repeat
)
{
if
(
!
cleanUpOnly
)
{
this
.
repeatPanel
(
panel
,
row
);
}
}
else
if
(
panel
.
repeatPanelId
&&
panel
.
repeatIteration
!==
this
.
iteration
)
{
// clean up old left overs
row
.
panels
=
_
.
without
(
row
.
panels
,
panel
);
j
=
j
-
1
;
}
}
row
.
panelSpanChanged
();
}
}
}
// returns a new row clone or reuses a clone from previous iteration
getRowClone
(
sourceRow
,
repeatIndex
,
sourceRowIndex
)
{
if
(
repeatIndex
===
0
)
{
return
sourceRow
;
}
var
i
,
panel
,
row
,
copy
;
var
sourceRowId
=
sourceRowIndex
+
1
;
// look for row to reuse
for
(
i
=
0
;
i
<
this
.
dashboard
.
rows
.
length
;
i
++
)
{
row
=
this
.
dashboard
.
rows
[
i
];
if
(
row
.
repeatRowId
===
sourceRowId
&&
row
.
repeatIteration
!==
this
.
iteration
)
{
copy
=
row
;
copy
.
copyPropertiesFromRowSource
(
sourceRow
);
break
;
}
}
if
(
!
copy
)
{
var
modelCopy
=
angular
.
copy
(
sourceRow
.
getSaveModel
());
copy
=
new
DashboardRow
(
modelCopy
);
this
.
dashboard
.
rows
.
splice
(
sourceRowIndex
+
repeatIndex
,
0
,
copy
);
// set new panel ids
for
(
i
=
0
;
i
<
copy
.
panels
.
length
;
i
++
)
{
panel
=
copy
.
panels
[
i
];
panel
.
id
=
this
.
dashboard
.
getNextPanelId
();
}
}
copy
.
repeat
=
null
;
copy
.
repeatRowId
=
sourceRowId
;
copy
.
repeatIteration
=
this
.
iteration
;
return
copy
;
}
// returns a new row clone or reuses a clone from previous iteration
repeatRow
(
row
,
rowIndex
)
{
var
variable
=
_
.
find
(
this
.
variables
,
{
name
:
row
.
repeat
});
if
(
!
variable
)
{
return
;
}
var
selected
,
copy
,
i
,
panel
;
if
(
variable
.
current
.
text
===
'All'
)
{
selected
=
variable
.
options
.
slice
(
1
,
variable
.
options
.
length
);
}
else
{
selected
=
_
.
filter
(
variable
.
options
,
{
selected
:
true
});
}
_
.
each
(
selected
,
(
option
,
index
)
=>
{
copy
=
this
.
getRowClone
(
row
,
index
,
rowIndex
);
copy
.
scopedVars
=
{};
copy
.
scopedVars
[
variable
.
name
]
=
option
;
for
(
i
=
0
;
i
<
copy
.
panels
.
length
;
i
++
)
{
panel
=
copy
.
panels
[
i
];
panel
.
scopedVars
=
{};
panel
.
scopedVars
[
variable
.
name
]
=
option
;
}
});
}
getPanelClone
(
sourcePanel
,
row
,
index
)
{
// if first clone return source
if
(
index
===
0
)
{
return
sourcePanel
;
}
var
i
,
tmpId
,
panel
,
clone
;
// first try finding an existing clone to use
for
(
i
=
0
;
i
<
row
.
panels
.
length
;
i
++
)
{
panel
=
row
.
panels
[
i
];
if
(
panel
.
repeatIteration
!==
this
.
iteration
&&
panel
.
repeatPanelId
===
sourcePanel
.
id
)
{
clone
=
panel
;
break
;
}
}
if
(
!
clone
)
{
clone
=
{
id
:
this
.
dashboard
.
getNextPanelId
()
};
row
.
panels
.
push
(
clone
);
}
// save id
tmpId
=
clone
.
id
;
// copy properties from source
angular
.
copy
(
sourcePanel
,
clone
);
// restore id
clone
.
id
=
tmpId
;
clone
.
repeatIteration
=
this
.
iteration
;
clone
.
repeatPanelId
=
sourcePanel
.
id
;
clone
.
repeat
=
null
;
return
clone
;
}
repeatPanel
(
panel
,
row
)
{
var
variable
=
_
.
find
(
this
.
variables
,
{
name
:
panel
.
repeat
});
if
(
!
variable
)
{
return
;
}
var
selected
;
if
(
variable
.
current
.
text
===
'All'
)
{
selected
=
variable
.
options
.
slice
(
1
,
variable
.
options
.
length
);
}
else
{
selected
=
_
.
filter
(
variable
.
options
,
{
selected
:
true
});
}
_
.
each
(
selected
,
(
option
,
index
)
=>
{
var
copy
=
this
.
getPanelClone
(
panel
,
row
,
index
);
copy
.
span
=
Math
.
max
(
12
/
selected
.
length
,
panel
.
minSpan
||
4
);
copy
.
scopedVars
=
copy
.
scopedVars
||
{};
copy
.
scopedVars
[
variable
.
name
]
=
option
;
});
}
}
coreModule
.
service
(
'dynamicDashboardSrv'
,
DynamicDashboardSrv
);
public/app/features/dashboard/export/exporter.ts
View file @
8bb9d92a
///<reference path="../../../headers/common.d.ts" />
import
config
from
'app/core/config'
;
import
_
from
'lodash'
;
import
{
D
ynamicDashboardSrv
}
from
'../dynamic_dashboard_srv
'
;
import
{
D
ashboardModel
}
from
'../dashboard_model
'
;
export
class
DashboardExporter
{
constructor
(
private
datasourceSrv
)
{
}
makeExportable
(
dashboard
)
{
var
dynSrv
=
new
DynamicDashboardSrv
();
makeExportable
(
dashboard
:
DashboardModel
)
{
// clean up repeated rows and panels,
// this is done on the live real dashboard instance, not on a clone
// so we need to undo this
// this is pretty hacky and needs to be changed
dynSrv
.
init
(
dashboard
);
dynSrv
.
process
({
cleanUpOnly
:
true
});
dashboard
.
cleanUpRepeats
();
var
saveModel
=
dashboard
.
getSaveModelClone
();
saveModel
.
id
=
null
;
// undo repeat cleanup
d
ynSrv
.
proces
s
();
d
ashboard
.
processRepeat
s
();
var
inputs
=
[];
var
requires
=
{};
...
...
public/app/features/dashboard/panel_model.ts
View file @
8bb9d92a
import
{
Emitter
}
from
'app/core/core'
;
import
_
from
'lodash'
;
export
interface
GridPos
{
x
:
number
;
...
...
@@ -21,6 +22,9 @@ export class PanelModel {
alert
?:
any
;
scopedVars
?:
any
;
repeat
?:
any
;
repeatIteration
?:
any
;
repeatPanelId
?:
any
;
repeatDirection
?:
any
;
// non persisted
fullscreen
:
boolean
;
...
...
@@ -34,6 +38,10 @@ export class PanelModel {
for
(
var
property
in
model
)
{
this
[
property
]
=
model
[
property
];
}
if
(
!
this
.
gridPos
)
{
this
.
gridPos
=
{
x
:
0
,
y
:
0
,
h
:
3
,
w
:
6
};
}
}
getSaveModel
()
{
...
...
@@ -43,7 +51,7 @@ export class PanelModel {
continue
;
}
model
[
property
]
=
this
[
property
]
;
model
[
property
]
=
_
.
cloneDeep
(
this
[
property
])
;
}
return
model
;
...
...
public/app/features/dashboard/specs/dynamic_dashboard_srv_specs.ts
View file @
8bb9d92a
This diff is collapsed.
Click to expand it.
public/app/features/dashboard/specs/exporter_specs.ts
View file @
8bb9d92a
...
...
@@ -10,7 +10,6 @@ describe('given dashboard with repeated panels', function() {
beforeEach
(
done
=>
{
dash
=
{
rows
:
[],
templating
:
{
list
:
[]
},
annotations
:
{
list
:
[]
},
};
...
...
@@ -47,26 +46,6 @@ describe('given dashboard with repeated panels', function() {
datasource
:
'gfdb'
,
});
dash
.
rows
.
push
({
repeat
:
'test'
,
panels
:
[
{
id
:
2
,
repeat
:
'apps'
,
datasource
:
'gfdb'
,
type
:
'graph'
},
{
id
:
3
,
repeat
:
null
,
repeatPanelId
:
2
},
{
id
:
4
,
datasource
:
'-- Mixed --'
,
targets
:
[{
datasource
:
'other'
}],
},
{
id
:
5
,
datasource
:
'$ds'
},
]
});
dash
.
rows
.
push
({
repeat
:
null
,
repeatRowId
:
1
,
panels
:
[],
});
dash
.
panels
=
[
{
id
:
6
,
datasource
:
'gfdb'
,
type
:
'graph'
},
{
id
:
7
},
...
...
@@ -78,6 +57,9 @@ describe('given dashboard with repeated panels', function() {
{
id
:
9
,
datasource
:
'$ds'
},
];
dash
.
panels
.
push
({
id
:
2
,
repeat
:
'apps'
,
datasource
:
'gfdb'
,
type
:
'graph'
});
dash
.
panels
.
push
({
id
:
3
,
repeat
:
null
,
repeatPanelId
:
2
});
var
datasourceSrvStub
=
{
get
:
sinon
.
stub
()};
datasourceSrvStub
.
get
.
withArgs
(
'gfdb'
).
returns
(
Promise
.
resolve
({
name
:
'gfdb'
,
...
...
@@ -110,14 +92,6 @@ describe('given dashboard with repeated panels', function() {
});
});
it
.
skip
(
'exported dashboard should not contain repeated panels'
,
function
()
{
expect
(
exported
.
rows
[
0
].
panels
.
length
).
to
.
be
(
3
);
});
it
.
skip
(
'exported dashboard should not contain repeated rows'
,
function
()
{
expect
(
exported
.
rows
.
length
).
to
.
be
(
1
);
});
it
(
'should replace datasource refs'
,
function
()
{
var
panel
=
exported
.
panels
[
0
];
expect
(
panel
.
datasource
).
to
.
be
(
"${DS_GFDB}"
);
...
...
public/app/features/dashboard/unsaved_changes_modal.ts
View file @
8bb9d92a
...
...
@@ -22,9 +22,9 @@ const template = `
</div>
<div class="confirm-modal-buttons">
<button type="button" class="btn btn-inverse" ng-click="ctrl.dismiss()">Cancel</button>
<button type="button" class="btn btn-danger" ng-click="ctrl.discard()">Discard</button>
<button type="button" class="btn btn-success" ng-click="ctrl.save()">Save</button>
<button type="button" class="btn btn-danger" ng-click="ctrl.discard()">Discard</button>
<button type="button" class="btn btn-inverse" ng-click="ctrl.dismiss()">Cancel</button>
</div>
</div>
</div>
...
...
public/app/features/panel/panel_ctrl.ts
View file @
8bb9d92a
import
config
from
'app/core/config'
;
import
_
from
'lodash'
;
import
$
from
'jquery'
;
import
{
profiler
}
from
'app/core/profiler
'
;
import
{
appEvents
,
profiler
}
from
'app/core/core
'
;
import
Remarkable
from
'remarkable'
;
import
{
CELL_HEIGHT
,
CELL_VMARGIN
}
from
'../dashboard/dashboard_model'
;
...
...
@@ -188,7 +188,30 @@ export class PanelCtrl {
});
}
removePanel
()
{
removePanel
(
ask
:
boolean
)
{
// confirm deletion
if
(
ask
!==
false
)
{
var
text2
,
confirmText
;
if
(
this
.
panel
.
alert
)
{
text2
=
"Panel includes an alert rule, removing panel will also remove alert rule"
;
confirmText
=
"YES"
;
}
appEvents
.
emit
(
'confirm-modal'
,
{
title
:
'Remove Panel'
,
text
:
'Are you sure you want to remove this panel?'
,
text2
:
text2
,
icon
:
'fa-trash'
,
confirmText
:
confirmText
,
yesText
:
'Remove'
,
onConfirm
:
()
=>
{
this
.
removePanel
(
false
);
}
});
return
;
}
this
.
dashboard
.
removePanel
(
this
.
panel
);
}
...
...
public/app/partials/panelgeneral.html
View file @
8bb9d92a
...
...
@@ -24,6 +24,12 @@
<option
value=
""
></option>
</select>
</div>
<div
class=
"gf-form"
>
<span
class=
"gf-form-label width-8"
>
Direction
</span>
<select
class=
"gf-form-input"
ng-model=
"ctrl.panel.repeatDirection"
ng-options=
"f for f in ['X', 'Y']"
>
<option
value=
""
></option>
</select>
</div>
</div>
<panel-links-editor
panel=
"ctrl.panel"
></panel-links-editor>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment