Merge branch 'master' of github.com:grafana/grafana into develop

This commit is contained in:
Torkel Ödegaard 2017-09-28 10:37:57 +02:00
commit 5849fbf04e
145 changed files with 676 additions and 42551 deletions

View File

@ -1,3 +0,0 @@
{
"directory": "public/vendor/"
}

View File

@ -1,21 +0,0 @@
{
"preset" : "default",
"lineBreak" : {
"before" : {
"VariableDeclarationWithoutInit" : 0,
},
"after": {
"AssignmentOperator": -1,
"ArgumentListArrayExpression": ">=1"
}
},
"whiteSpace" : {
"before" : {
},
"after" : {
}
}
}

View File

@ -15,13 +15,20 @@
* **Graph**: Add support for local formating in axis. [#1395](https://github.com/grafana/grafana/issues/1395), thx [@m0nhawk](https://github.com/m0nhawk)
* **Jaeger**: Add support for open tracing using jaeger in Grafana. [#9213](https://github.com/grafana/grafana/pull/9213)
* **Unit types**: New date & time unit types added, useful in singlestat to show dates & times. [#3678](https://github.com/grafana/grafana/issues/3678), [#6710](https://github.com/grafana/grafana/issues/6710), [#2764](https://github.com/grafana/grafana/issues/6710)
* **CLI**: Make it possible to install plugins from any url [#5873](https://github.com/grafana/grafana/issues/5873)
* **Prometheus**: Add support for instant queries [#5765](https://github.com/grafana/grafana/issues/5765), thx [@mtanda](https://github.com/mtanda)
## Breaking changes
* **Metrics**: The metric structure for internal metrics about Grafana published to graphite has changed. This might break dashboards for internal metrics.
## Minor
* **SMTP**: Make it possible to set specific EHLO for smtp client. [#9319](https://github.com/grafana/grafana/issues/9319)
* **Dataproxy**: Allow grafan to renegotiate tls connection [#9250](https://github.com/grafana/grafana/issues/9250)
# 4.5.2 (unreleased)
# 4.5.2 (2017-09-22)
## Fixes
* **Graphite**: Fix for issues with jsonData & graphiteVersion null errors [#9258](https://github.com/grafana/grafana/issues/9258)
* **Graphite**: Fix for Grafana internal metrics to Graphite sending NaN values [#9279](https://github.com/grafana/grafana/issues/9279)
* **HTTP API**: Fix for HEAD method requests [#9307](https://github.com/grafana/grafana/issues/9307)
* **Templating**: Fix for duplicate template variable queries when refresh is set to time range change [#9185](https://github.com/grafana/grafana/issues/9185)
* **Metrics**: dont write NaN values to graphite [#9279](https://github.com/grafana/grafana/issues/9279)
# 4.5.1 (2017-09-15)
@ -29,6 +36,9 @@
## Fixes
* **MySQL**: Fixed issue with query editor not showing [#9247](https://github.com/grafana/grafana/issues/9247)
## Breaking changes
* **Metrics**: The metric structure for internal metrics about Grafana published to graphite has changed. This might break dashboards for internal metrics.
# 4.5.0 (2017-09-14)
## Fixes & Enhancements since beta1

View File

@ -1,24 +0,0 @@
{
"name": "grafana",
"version": "2.0.2",
"homepage": "https://github.com/grafana/grafana",
"authors": [],
"license": "Apache 2.0",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"public/vendor/",
"test",
"tests"
],
"dependencies": {
"angular": "1.6.1",
"angular-route": "1.6.1",
"angular-mocks": "1.6.1",
"angular-sanitize": "1.6.1",
"angular-native-dragdrop": "1.2.2",
"angular-bindonce": "0.3.3",
"clipboard": "^1.5.16"
}
}

View File

@ -318,6 +318,7 @@ key_file =
skip_verify = false
from_address = admin@grafana.localhost
from_name = Grafana
ehlo_identity =
[emails]
welcome_email_on_sign_up = false
@ -476,6 +477,8 @@ provider =
[external_image_storage.s3]
bucket_url =
bucket =
region =
access_key =
secret_key =

View File

@ -295,6 +295,8 @@
;skip_verify = false
;from_address = admin@grafana.localhost
;from_name = Grafana
# EHLO identity in SMTP dialog (defaults to instance_name)
;ehlo_identity = dashboard.example.com
[emails]
;welcome_email_on_sign_up = false
@ -420,7 +422,8 @@
;provider =
[external_image_storage.s3]
;bucket_url =
;bucket =
;region =
;access_key =
;secret_key =

View File

@ -17,20 +17,19 @@ alerting:
- targets:
- "127.0.0.1:9093"
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
# The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
- job_name: 'prometheus'
# Override the global default and scrape targets from this job every 5 seconds.
scrape_interval: 10s
scrape_timeout: 10s
# metrics_path defaults to '/metrics'
# scheme defaults to 'http'.
static_configs:
#- targets: ['localhost:9090', '172.17.0.1:9091', '172.17.0.1:9100', '172.17.0.1:9150']
- targets: ['localhost:9090', '127.0.0.1:9091', '127.0.0.1:9100', '127.0.0.1:9150']
- targets: ['localhost:9090']
- job_name: 'node_exporter'
static_configs:
- targets: ['127.0.0.1:9100']
- job_name: 'fake-data-gen'
static_configs:
- targets: ['127.0.0.1:9091']
- job_name: 'grafana'
static_configs:
- targets: ['127.0.0.1:3000']

View File

@ -12,42 +12,23 @@ weight = 4
# Dashboard List Panel
The dashboard list panel allows you to display dynamic links to other dashboards. The list can be configured to use starred dashboards, a search query and/or dashboard tags.
{{< docs-imagebox img="/img/docs/v45/dashboard-list-panels.png" max-width= "800px" >}}
<img class="no-shadow" src="/img/docs/v2/dashboard_list_panels.png">
The dashboard list panel allows you to display dynamic links to other dashboards. The list can be configured to use starred dashboards, recently viewed dashboards, a search query and/or dashboard tags.
> On each dashboard load, the dashlist panel will re-query the dashboard list, always providing the most up to date results.
## Mode: Starred Dashboards
## Dashboard List Options
The `starred` dashboard selection displays starred dashboards, up to the number specified in the `Limit Number to` field, in alphabetical order. On dashboard load, the dashlist panel will re-query the favorites to appear in dashboard list panel, always providing the most up to date results.
{{< docs-imagebox img="/img/docs/v45/dashboard-list-options.png" max-width="600px" class="docs-image--no-shadow">}}
<img class="no-shadow" src="/img/docs/v2/dashboard_list_config_starred.png">
## Mode: Search Dashboards
The panel may be configured to search by either string query or tag(s). On dashboard load, the dashlist panel will re-query the dashboard list, always providing the most up to date results.
To configure dashboard list in this manner, select `search` from the Mode select box. When selected, the Search Options section will appear.
Name | Description
------------ | -------------
Mode | Set search or starred mode
Query | If in search mode specify the search query
Tags | if in search mode specify dashboard tags to search for
Limit number to | Specify the maximum number of dashboards
### Search by string
To search by a string, enter a search query in the `Search Options: Query` field. Queries are case-insensitive, and partial values are accepted.
<img class="no-shadow" src="/img/docs/v2/dashboard_list_config_string.png">
### Search by tag
To search by one or more tags, enter your selection in the `Search Options: Tags:` field. Note that existing tags will not appear as you type, and *are* case sensitive. To see a list of existing tags, you can always return to the dashboard, open the Dashboard Picker at the top and click `tags` link in the search bar.
<img class="no-shadow" src="/img/docs/v2/dashboard_list_config_tags.png">
1. `Starred`: The starred dashboard selection displays starred dashboards in alphabetical order.
2. `Recently Viewed`: The recently viewed dashboard selection displays recently viewed dashboards in alphabetical order.
3. `Search`: The search dashboard selection displays dashboards by search query or tag(s).
4. `Show Headings`: When show headings is ticked the choosen list selection(Starred, Recently Viewed, Search) is shown as a heading.
5. `Max Items`: Max items set the maximum of items in a list.
6. `Query`: Here is where you enter your query you want to search by. Queries are case-insensitive, and partial values are accepted.
7. `Tags`: Here is where you enter your tag(s) you want to search by. Note that existing tags will not appear as you type, and *are* case sensitive. To see a list of existing tags, you can always return to the dashboard, open the Dashboard Picker at the top and click `tags` link in the search bar.
> When multiple tags and strings appear, the dashboard list will display those matching ALL conditions.

View File

@ -13,14 +13,15 @@ weight = 1
The main panel in Grafana is simply named Graph. It provides a very rich set of graphing options.
<img src="/img/docs/v1/graph_overview.png" class="no-shadow">
{{< docs-imagebox img="/img/docs/v45/graph_overview.png" class="docs-image--no-shadow" max-width= "900px" >}}
Clicking the title for a panel exposes a menu. The `edit` option opens additional configuration
options for the panel.
1. Clicking the title for a panel exposes a menu. The `edit` option opens additional configuration options for the panel.
2. Click to open color & axis selection.
3. Click to only show this series. Shift/Ctrl + click to hide series.
## General
![](/img/docs/v43/graph_general.png)
{{< docs-imagebox img="/img/docs/v43/graph_general.png" max-width= "900px" >}}
The general tab allows customization of a panel's appearance and menu options.
@ -52,7 +53,7 @@ options.
## Axes
![](/img/docs/v43/graph_axes_grid_options.png)
{{< docs-imagebox img="/img/docs/v43/graph_axes_grid_options.png" max-width= "900px" >}}
The Axes tab controls the display of axes, grids and legend. The ``Left Y`` and ``Right Y`` can be customized using:
@ -101,7 +102,7 @@ It is just the sum of all data points received by Grafana.
## Display styles
![](/img/docs/v43/graph_display_styles.png)
{{< docs-imagebox img="/img/docs/v43/graph_display_styles.png" max-width= "900px" >}}
Display styles control visual properties of the graph.
@ -156,4 +157,6 @@ There is an option under Series overrides to draw lines as dashes. Set Dashes to
## Time Range
![](/img/docs/v2/graph_time_range.png)
The time range tab allows you to override the dashboard time range and specify a panel specific time. Either through a relative from now time option or through a timeshift.
{{< docs-imagebox img="/img/docs/v45/graph-time-range.png" max-width= "900px" >}}

View File

@ -12,7 +12,7 @@ weight = 2
# Singlestat Panel
![](/img/docs/v1/singlestat_panel2.png)
{{< docs-imagebox img="/img/docs/v45/singlestat-panel.png" max-width="900px" >}}
The Singlestat Panel allows you to show the one main summary stat of a SINGLE series. It reduces the series into a single number (by looking at the max, min, average, or sum of values in the series). Singlestat also provides thresholds to color the stat or the Panel background. It can also translate the single number into a text value, and show a sparkline summary of the series.
@ -20,11 +20,9 @@ The Singlestat Panel allows you to show the one main summary stat of a SINGLE se
The singlestat panel has a normal query editor to allow you define your exact metric queries like many other Panels. Through the Options tab, you can access the Singlestat-specific functionality.
<img class="no-shadow" src="/img/docs/v1/Singlestat-BaseSettings.png">
{{< docs-imagebox img="/img/docs/v45/singlestat-value-options.png" class="docs-image--no-shadow" max-width= "900px" >}}
1. `Big Value`: Big Value refers to how we display the main stat for the Singlestat Panel. This is always a single value that is displayed in the Panel in between two strings, `Prefix` and `Suffix`. The single number is calculated by choosing a function (min,max,average,current,total) of your metric query. This functions reduces your query into a single numeric value.
2. `Font Size`: You can use this section to select the font size of the different texts in the Singlestat Panel, i.e. prefix, value and postfix.
3. `Values`: The Value fields let you set the function (min, max, average, current, total, first, delta, range) that your entire query is reduced into a single value with. You can also set the font size of the Value field and font-size (as a %) of the metric query that the Panel is configured with. This reduces the entire query into a single summary value that is displayed.
1. `Stats`: The Stats field let you set the function (min, max, average, current, total, first, delta, range) that your entire query is reduced into a single value with. This reduces the entire query into a single summary value that is displayed.
* `min` - The smallest value in the series
* `max` - The largest value in the series
* `avg` - The average of all the non-null values in the series
@ -34,47 +32,64 @@ The singlestat panel has a normal query editor to allow you define your exact me
* `delta` - The total incremental increase (of a counter) in the series. An attempt is made to account for counter resets, but this will only be accurate for single instance metrics. Used to show total counter increase in time series.
* `diff` - The difference betwen 'current' (last value) and 'first'.
* `range` - The difference between 'min' and 'max'. Useful the show the range of change for a gauge.
4. `Prefix/Postfix`: The Prefix/Postfix fields let you define a custom label and font-size (as a %) to appear *before/after* the value. The `$__name` variable can be used here to use the series name or alias from the metric query.
5. `Units`: Units are appended to the the Singlestat within the panel, and will respect the color and threshold settings for the value.
6. `Decimals`: The Decimal field allows you to override the automatic decimal precision, and set it explicitly.
2. `Prefix/Postfix`: The Prefix/Postfix fields let you define a custom label to appear *before/after* the value. The `$__name` variable can be used here to use the series name or alias from the metric query.
3. `Units`: Units are appended to the the Singlestat within the panel, and will respect the color and threshold settings for the value.
4. `Decimals`: The Decimal field allows you to override the automatic decimal precision, and set it explicitly.
5. `Font Size`: You can use this section to select the font size of the different texts in the Singlestat Panel, i.e. prefix, value and postfix.
### Coloring
The coloring options of the Singlestat Panel config allow you to dynamically change the colors based on the Singlestat value.
<img class="no-shadow" src="/img/docs/v1/Singlestat-Coloring.png">
{{< docs-imagebox img="/img/docs/v45/singlestat-color-options.png" max-width="500px" class="docs-image--right docs-image--no-shadow">}}
1. `Background`: This checkbox applies the configured thresholds and colors to the entirety of the Singlestat Panel background.
2. `Value`: This checkbox applies the configured thresholds and colors to the summary stat.
3. `Thresholds`: Change the background and value colors dynamically within the panel, depending on the Singlestat value. The threshold field accepts **2 comma-separated** values which represent 3 ranges that correspond to the three colors directly to the right. For example: if the thresholds are 70, 90 then the first color represents < 70, the second color represents between 70 and 90 and the third color represents > 90.
4. `Colors`: Select a color and opacity
2. `Thresholds`: Change the background and value colors dynamically within the panel, depending on the Singlestat value. The threshold field accepts **2 comma-separated** values which represent 3 ranges that correspond to the three colors directly to the right. For example: if the thresholds are 70, 90 then the first color represents < 70, the second color represents between 70 and 90 and the third color represents > 90.
3. `Colors`: Select a color and opacity
4. `Value`: This checkbox applies the configured thresholds and colors to the summary stat.
5. `Invert order`: This link toggles the threshold color order.</br>For example: Green, Orange, Red (<img class="no-shadow" src="/img/docs(v1/gyr.png">) will become Red, Orange, Green (<img class="no-shadow" src="/img/docs/v1/ryg.png">).
### Spark Lines
Sparklines are a great way of seeing the historical data related to the summary stat, providing valuable context at a glance. Sparklines act differently than traditional Graph Panels and do not include x or y axis, coordinates, a legend, or ability to interact with the graph.
<img class="no-shadow" src="/img/docs/v1/Singlestat-Sparklines.png">
{{< docs-imagebox img="/img/docs/v45/singlestat-spark-options.png" max-width="500px" class="docs-image--right docs-image--no-shadow">}}
1. `Show`: The show checkbox will toggle whether the spark line is shown in the Panel. When unselected, only the Singlestat value will appear.
2. `Background`: Check if you want the sparklines to take up the full panel width, or uncheck if they should be below the main Singlestat value.
2. `Full Height`: Check if you want the sparklines to take up the full panel height, or uncheck if they should be below the main Singlestat value.
3. `Line Color`: This color selection applies to the color of the sparkline itself.
4. `Fill Color`: This color selection applies to the area below the sparkline.
<div class="clearfix"></div>
> ***Pro-tip:*** Reduce the opacity on fill colors for nice looking panels.
### Gauge
Gauges gives a clear picture of how high a value is in it's context. It's a great way to see if a value is close to the thresholds. The gauge uses the colors set in the color options.
{{< docs-imagebox img="/img/docs/v45/singlestat-gauge-options.png" max-width="500px" class="docs-image--right docs-image--no-shadow">}}
1. `Show`: The show checkbox will toggle wether the gauge is shown in the panel. When unselected, only the Singlestat value will appear.
2. `Min/Max`: This sets the start and end point for the gauge.
3. `Threshold Labels`: Check if you want to show the threshold labels. Thresholds are set in the color options.
4. `Threshold Markers`: Check if you want to have a second meter showing the thresholds.
<div class="clearfix"></div>
### Value to text mapping
{{< docs-imagebox img="/img/docs/v45/singlestat-value-mapping.png" class="docs-image--right docs-image--no-shadow">}}
Value to text mapping allows you to translate the value of the summary stat into explicit text. The text will respect all styling, thresholds and customization defined for the value. This can be useful to translate the number of the main Singlestat value into a context-specific human-readable word or message.
<img class="no-shadow" src="/img/docs/v1/Singlestat-ValueMapping.png">
<div class="clearfix"></div>
## Troubleshooting
### Multiple Series Error
<img class="no-shadow" src="/img/docs/v2/Singlestat-MultiSeriesError.png">
{{< docs-imagebox img="/img/docs/v45/singelstat-multiple-series-error.png" class="docs-image--right docs-image--no-shadow">}}
Grafana 2.5 introduced stricter checking for multiple-series on singlestat panels. In previous versions, the panel logic did not verify that only a single series was used, and instead, displayed the first series encountered. Depending on your data source, this could have lead to inconsistent data being shown and/or a general confusion about which metric was being displayed.

View File

@ -161,6 +161,7 @@ Only works with Basic Authentication (username and password). See [introduction]
"enabled":"false",
"from_address":"admin@grafana.localhost",
"from_name":"Grafana",
"ehlo_identity":"dashboard.example.com",
"host":"localhost:25",
"key_file":"",
"password":"************",

View File

@ -593,6 +593,9 @@ Address used when sending out emails, defaults to `admin@grafana.localhost`
### from_name
Name to be used when sending out emails, defaults to `Grafana`
### ehlo_identity
Name to be used as client identity for EHLO in SMTP dialog, defaults to instance_name.
## [log]
### mode
@ -648,12 +651,16 @@ These options control how images should be made public so they can be shared on
You can choose between (s3, webdav, gcs). If left empty Grafana will ignore the upload action.
## [external_image_storage.s3]
### bucket
Bucket name for S3. e.g. grafana.snapshot
### region
Region name for S3. e.g. 'us-east-1', 'cn-north-1', etc
### bucket_url
(for backward compatibility, only works when no bucket or region are configured)
Bucket URL for S3. AWS region can be specified within URL or defaults to 'us-east-1', e.g.
- http://grafana.s3.amazonaws.com/
- https://grafana.s3-ap-southeast-2.amazonaws.com/
- https://grafana.s3-cn-north-1.amazonaws.com.cn
### access_key
Access key. e.g. AAAAAAAAAAAAAAAAAAAA

View File

@ -15,7 +15,7 @@ weight = 1
Description | Download
------------ | -------------
Stable for Debian-based Linux | [grafana_4.5.1_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.5.1_amd64.deb)
Stable for Debian-based Linux | [grafana_4.5.2_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.5.2_amd64.deb)
<!-- Beta for Debian-based Linux | [grafana_4.5.0-beta1_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.5.0-beta1_amd64.deb) -->
@ -26,18 +26,18 @@ installation.
```bash
wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.5.1_amd64.deb
wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.5.2_amd64.deb
sudo apt-get install -y adduser libfontconfig
sudo dpkg -i grafana_4.5.1_amd64.deb
sudo dpkg -i grafana_4.5.2_amd64.deb
```
<!--
## Install Latest Beta
```bash
wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.5.0-beta1_amd64.deb
wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.5.2-beta1_amd64.deb
sudo apt-get install -y adduser libfontconfig
sudo dpkg -i grafana_4.5.0-beta1_amd64.deb
sudo dpkg -i grafana_4.5.2-beta1_amd64.deb
```
-->

View File

@ -43,3 +43,35 @@ To upgrade grafana if you've installed from HEAD:
```
brew reinstall --HEAD grafana/grafana/grafana
```
### Starting Grafana
To start Grafana using homebrew services first make sure homebrew/services is installed.
```
brew tap homebrew/services
```
Then start Grafana using:
```
brew services start grafana
```
### Configuration
The Configuration file should be located at `/usr/local/etc/grafana/grafana.ini`.
### Logs
The log file should be located at `/usr/local/var/log/grafana/grafana.log`.
### Plugins
If you want to manually install a plugin place it here: `/usr/local/var/lib/grafana/plugins`.
### Database
The default sqlite database is located at `/usr/local/var/lib/grafana`

View File

@ -15,7 +15,7 @@ weight = 2
Description | Download
------------ | -------------
Stable for CentOS / Fedora / OpenSuse / Redhat Linux | [4.5.1 (x86-64 rpm)](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.5.1-1.x86_64.rpm)
Stable for CentOS / Fedora / OpenSuse / Redhat Linux | [4.5.2 (x86-64 rpm)](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.5.2-1.x86_64.rpm)
<!-- Latest Beta for CentOS / Fedora / OpenSuse / Redhat Linux | [4.5.0-beta1 (x86-64 rpm)](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.5.0-beta1.x86_64.rpm) -->
@ -26,19 +26,19 @@ installation.
You can install Grafana using Yum directly.
$ sudo yum install https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.5.1-1.x86_64.rpm
$ sudo yum install https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.5.2-1.x86_64.rpm
Or install manually using `rpm`.
#### On CentOS / Fedora / Redhat:
$ wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.5.1-1.x86_64.rpm
$ wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.5.2-1.x86_64.rpm
$ sudo yum install initscripts fontconfig
$ sudo rpm -Uvh grafana-4.5.1-1.x86_64.rpm
$ sudo rpm -Uvh grafana-4.5.2-1.x86_64.rpm
#### On OpenSuse:
$ sudo rpm -i --nodeps grafana-4.5.1-1.x86_64.rpm
$ sudo rpm -i --nodeps grafana-4.5.2-1.x86_64.rpm
## Install via YUM Repository

View File

@ -13,7 +13,7 @@ weight = 3
Description | Download
------------ | -------------
Latest stable package for Windows | [grafana.4.5.1.windows-x64.zip](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.5.1.windows-x64.zip)
Latest stable package for Windows | [grafana.4.5.2.windows-x64.zip](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.5.2.windows-x64.zip)
Read [Upgrading Grafana]({{< relref "installation/upgrading.md" >}}) for tips and guidance on updating an existing
installation.

View File

@ -72,6 +72,11 @@ The Download URL from Grafana.com API is in this form:
`https://grafana.com/api/plugins/<plugin id>/versions/<version number>/download`
You can specify a local URL by using the `--pluginUrl` option.
```
grafana-cli --pluginUrl https://nexus.company.com/grafana/plugins/<plugin-id>-<plugin-version>.zip plugins install <plugin-id>
```
To manually install a Plugin via the Grafana.com API:
1. Find the plugin you want to download, the plugin id can be found on the Installation Tab on the plugin's page on Grafana.com. In this example, the plugin id is `jdbranham-diagram-panel`:

View File

@ -1,34 +0,0 @@
+++
title = "Keyboard shortcuts"
keywords = ["grafana", "dashboard", "documentation", "shortcuts"]
type = "docs"
[menu.docs]
parent = "dashboard_features"
weight = 8
+++
# Keyboard Shortcuts
No mouse? No problem. Grafana has extensive keyboard shortcuts to allow you to navigate throughout the interface. This comes in especially handy when dealing with dealing with single-purpose machines powering on-wall displays that may not have a mouse available.
## Dashboard Keyboard Shortcuts
Press `Shift`+`?` to open the keyboard shortcut dialog from anywhere within the dashboard views.
<img class="no-shadow" src="/img/docs/v2/Grafana-Keyboard-Shortcuts.gif" style="width:80%;">
|Shortcut|Action|
|---|---|
|`Esc`|Exit fullscreen edit/view mode, close search or any editor view|
|`F`|Open dashboard search view (also contains import/playlist controls)|
|`R`|Refresh (Fetches new data and rerenders panels)|
|`CTRL`+`S`|Save dashboard|
|`CTRL`+`H`|Hide row controls|
|`CTRL`+`Z`|Zoom out|
|`CTRL`+`O`|Enable/Disable shared graph crosshair|
**Note**: Grafana keyboard shortcuts are the same across operating system.
Have a suggestion for a new keyboard shortcut? Let us know.

View File

@ -1,4 +1,4 @@
{
"stable": "4.4.1",
"testing": "4.4.1"
"stable": "4.5.2",
"testing": "4.5.2"
}

View File

@ -64,6 +64,13 @@
"dependencies": {
"@types/enzyme": "^2.8.8",
"ace-builds": "^1.2.8",
"angular": "^1.6.6",
"angular-bindonce": "^0.3.1",
"angular-mocks": "^1.6.6",
"angular-native-dragdrop": "^1.2.2",
"angular-route": "^1.6.6",
"angular-sanitize": "^1.6.6",
"clipboard": "^1.7.1",
"eventemitter3": "^2.0.2",
"gaze": "^1.1.2",
"gridstack": "https://github.com/grafana/gridstack.js#grafana",

View File

@ -1,5 +1,5 @@
#! /usr/bin/env bash
version=4.5.1
version=4.5.2
wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_${version}_amd64.deb
@ -8,15 +8,15 @@ package_cloud push grafana/stable/debian/wheezy grafana_${version}_amd64.deb
package_cloud push grafana/stable/debian/stretch grafana_${version}_amd64.deb
package_cloud push grafana/testing/debian/jessie grafana_${version}_amd64.deb
package_cloud push grafana/testing/debian/wheezy grafana_${version}_amd64.deb
package_cloud push grafana/testing/debian/stretch grafana_${version}_amd64.deb
package_cloud push grafana/testing/debian/wheezy grafana_${version}_amd64.deb --verbose
package_cloud push grafana/testing/debian/stretch grafana_${version}_amd64.deb --verbose
wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-${version}-1.x86_64.rpm
package_cloud push grafana/testing/el/6 grafana-${version}-1.x86_64.rpm
package_cloud push grafana/testing/el/7 grafana-${version}-1.x86_64.rpm
package_cloud push grafana/testing/el/6 grafana-${version}-1.x86_64.rpm --verbose
package_cloud push grafana/testing/el/7 grafana-${version}-1.x86_64.rpm --verbose
package_cloud push grafana/stable/el/7 grafana-${version}-1.x86_64.rpm
package_cloud push grafana/stable/el/6 grafana-${version}-1.x86_64.rpm
package_cloud push grafana/stable/el/7 grafana-${version}-1.x86_64.rpm --verbose
package_cloud push grafana/stable/el/6 grafana-${version}-1.x86_64.rpm --verbose
rm grafana*.{deb,rpm}

View File

@ -17,8 +17,11 @@ import (
)
var pluginProxyTransport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
Renegotiation: tls.RenegotiateFreelyAsClient,
},
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,

View File

@ -43,7 +43,7 @@ func QueryMetrics(c *middleware.Context, reqDto dtos.MetricRequest) Response {
})
}
resp, err := tsdb.HandleRequest(context.Background(), request)
resp, err := tsdb.HandleRequest(context.Background(), dsQuery.Result, request)
if err != nil {
return ApiError(500, "Metric request error", err)
}
@ -100,16 +100,17 @@ func GetTestDataRandomWalk(c *middleware.Context) Response {
timeRange := tsdb.NewTimeRange(from, to)
request := &tsdb.TsdbQuery{TimeRange: timeRange}
dsInfo := &models.DataSource{Type: "grafana-testdata-datasource"}
request.Queries = append(request.Queries, &tsdb.Query{
RefId: "A",
IntervalMs: intervalMs,
Model: simplejson.NewFromAny(&util.DynMap{
"scenario": "random_walk",
}),
DataSource: &models.DataSource{Type: "grafana-testdata-datasource"},
DataSource: dsInfo,
})
resp, err := tsdb.HandleRequest(context.Background(), request)
resp, err := tsdb.HandleRequest(context.Background(), dsInfo, request)
if err != nil {
return ApiError(500, "Metric request error", err)
}

View File

@ -19,6 +19,7 @@ type CommandLine interface {
PluginDirectory() string
RepoDirectory() string
PluginURL() string
}
type contextCommandLine struct {
@ -44,3 +45,7 @@ func (c *contextCommandLine) PluginDirectory() string {
func (c *contextCommandLine) RepoDirectory() string {
return c.GlobalString("repo")
}
func (c *contextCommandLine) PluginURL() string {
return c.GlobalString("pluginUrl")
}

View File

@ -101,3 +101,7 @@ func (fcli *FakeCommandLine) RepoDirectory() string {
func (fcli *FakeCommandLine) PluginDirectory() string {
return fcli.GlobalString("pluginsDir")
}
func (fcli *FakeCommandLine) PluginURL() string {
return fcli.GlobalString("pluginUrl")
}

View File

@ -58,37 +58,39 @@ func installCommand(c CommandLine) error {
}
func InstallPlugin(pluginName, version string, c CommandLine) error {
plugin, err := s.GetPlugin(pluginName, c.RepoDirectory())
pluginFolder := c.PluginDirectory()
if err != nil {
return err
downloadURL := c.PluginURL()
if downloadURL == "" {
plugin, err := s.GetPlugin(pluginName, c.RepoDirectory())
if err != nil {
return err
}
v, err := SelectVersion(plugin, version)
if err != nil {
return err
}
if version == "" {
version = v.Version
}
downloadURL = fmt.Sprintf("%s/%s/versions/%s/download",
c.GlobalString("repo"),
pluginName,
version)
}
v, err := SelectVersion(plugin, version)
if err != nil {
return err
}
if version == "" {
version = v.Version
}
downloadURL := fmt.Sprintf("%s/%s/versions/%s/download",
c.GlobalString("repo"),
pluginName,
version)
logger.Infof("installing %v @ %v\n", plugin.Id, version)
logger.Infof("installing %v @ %v\n", pluginName, version)
logger.Infof("from url: %v\n", downloadURL)
logger.Infof("into: %v\n", pluginFolder)
logger.Info("\n")
err = downloadFile(plugin.Id, pluginFolder, downloadURL)
err := downloadFile(pluginName, pluginFolder, downloadURL)
if err != nil {
return err
}
logger.Infof("%s Installed %s successfully \n", color.GreenString("✔"), plugin.Id)
logger.Infof("%s Installed %s successfully \n", color.GreenString("✔"), pluginName)
res, _ := s.ReadPlugin(pluginFolder, pluginName)
for _, v := range res.Dependencies.Plugins {

View File

@ -38,6 +38,12 @@ func main() {
Value: "https://grafana.com/api/plugins",
EnvVar: "GF_PLUGIN_REPO",
},
cli.StringFlag{
Name: "pluginUrl",
Usage: "Full url to the plugin zip file instead of downloading the plugin from grafana.com/api",
Value: "",
EnvVar: "GF_PLUGIN_URL",
},
cli.BoolFlag{
Name: "debug, d",
Usage: "enable debug logging",

View File

@ -14,6 +14,7 @@ import (
"net/http"
_ "net/http/pprof"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
@ -29,7 +30,7 @@ import (
_ "github.com/grafana/grafana/pkg/tsdb/testdata"
)
var version = "4.1.0"
var version = "4.6.0"
var commit = "NA"
var buildstamp string
var build_date string
@ -80,6 +81,8 @@ func main() {
setting.BuildCommit = commit
setting.BuildStamp = buildstampInt64
metrics.M_Grafana_Version.WithLabelValues(version).Set(1)
server := NewGrafanaServer()
server.Start()
}

View File

@ -28,15 +28,21 @@ func NewImageUploader() (ImageUploader, error) {
return nil, err
}
bucket := s3sec.Key("bucket").MustString("")
region := s3sec.Key("region").MustString("")
bucketUrl := s3sec.Key("bucket_url").MustString("")
accessKey := s3sec.Key("access_key").MustString("")
secretKey := s3sec.Key("secret_key").MustString("")
info, err := getRegionAndBucketFromUrl(bucketUrl)
if err != nil {
return nil, err
if bucket == "" || region == "" {
info, err := getRegionAndBucketFromUrl(bucketUrl)
if err != nil {
return nil, err
}
bucket = info.bucket
region = info.region
}
return NewS3Uploader(info.region, info.bucket, "public-read", accessKey, secretKey), nil
return NewS3Uploader(region, bucket, "public-read", accessKey, secretKey), nil
case "webdav":
webdavSec, err := setting.Cfg.GetSection("external_image_storage.webdav")
if err != nil {

View File

@ -9,6 +9,7 @@ import (
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/grafana/grafana/pkg/log"
@ -54,8 +55,10 @@ func (u *S3Uploader) Upload(ctx context.Context, imageDiskPath string) (string,
Credentials: creds,
}
s3_endpoint, _ := endpoints.DefaultResolver().EndpointFor("s3", u.region)
key := util.GetRandomString(20) + ".png"
log.Debug("Uploading image to s3", "bucket = ", u.bucket, ", key = ", key)
image_url := s3_endpoint.URL + "/" + u.bucket + "/" + key
log.Debug("Uploading image to s3", "url = ", image_url)
file, err := os.Open(imageDiskPath)
if err != nil {
@ -78,10 +81,5 @@ func (u *S3Uploader) Upload(ctx context.Context, imageDiskPath string) (string,
if err != nil {
return "", err
}
if u.region == "us-east-1" {
return "https://" + u.bucket + ".s3.amazonaws.com/" + key, nil
} else {
return "https://" + u.bucket + ".s3-" + u.region + ".amazonaws.com/" + key, nil
}
return image_url, nil
}

View File

@ -56,6 +56,7 @@ var (
M_StatTotal_Users prometheus.Gauge
M_StatTotal_Orgs prometheus.Gauge
M_StatTotal_Playlists prometheus.Gauge
M_Grafana_Version *prometheus.GaugeVec
)
func init() {
@ -263,6 +264,13 @@ func init() {
Help: "total amount of playlists",
Namespace: exporterName,
})
M_Grafana_Version = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "info",
Help: "Information about the Grafana",
Namespace: exporterName,
}, []string{"version"})
}
func initMetricVars(settings *MetricSettings) {
@ -298,7 +306,8 @@ func initMetricVars(settings *MetricSettings) {
M_StatTotal_Dashboards,
M_StatTotal_Users,
M_StatTotal_Orgs,
M_StatTotal_Playlists)
M_StatTotal_Playlists,
M_Grafana_Version)
go instrumentationLoop(settings)
}

View File

@ -48,6 +48,7 @@ func (ds *DataSource) GetHttpTransport() (*http.Transport, error) {
transport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
Renegotiation: tls.RenegotiateFreelyAsClient,
},
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{

View File

@ -112,7 +112,7 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *
req := c.getRequestForAlertRule(getDsInfo.Result, timeRange)
result := make(tsdb.TimeSeriesSlice, 0)
resp, err := c.HandleRequest(context.Ctx, req)
resp, err := c.HandleRequest(context.Ctx, getDsInfo.Result, req)
if err != nil {
if err == gocontext.DeadlineExceeded {
return nil, fmt.Errorf("Alert execution exceeded the timeout")

View File

@ -168,7 +168,7 @@ func (ctx *queryConditionTestContext) exec() (*alerting.ConditionResult, error)
ctx.condition = condition
condition.HandleRequest = func(context context.Context, req *tsdb.TsdbQuery) (*tsdb.Response, error) {
condition.HandleRequest = func(context context.Context, dsInfo *m.DataSource, req *tsdb.TsdbQuery) (*tsdb.Response, error) {
return &tsdb.Response{
Results: map[string]*tsdb.QueryResult{
"A": {Series: ctx.series},

View File

@ -101,7 +101,11 @@ func createDialer() (*gomail.Dialer, error) {
d := gomail.NewDialer(host, iPort, setting.Smtp.User, setting.Smtp.Password)
d.TLSConfig = tlsconfig
d.LocalName = setting.InstanceName
if setting.Smtp.EhloIdentity != "" {
d.LocalName = setting.Smtp.EhloIdentity
} else {
d.LocalName = setting.InstanceName
}
return d, nil
}

View File

@ -1,15 +1,16 @@
package setting
type SmtpSettings struct {
Enabled bool
Host string
User string
Password string
CertFile string
KeyFile string
FromAddress string
FromName string
SkipVerify bool
Enabled bool
Host string
User string
Password string
CertFile string
KeyFile string
FromAddress string
FromName string
EhloIdentity string
SkipVerify bool
SendWelcomeEmailOnSignUp bool
TemplatesPattern string
@ -25,6 +26,7 @@ func readSmtpSettings() {
Smtp.KeyFile = sec.Key("key_file").String()
Smtp.FromAddress = sec.Key("from_address").String()
Smtp.FromName = sec.Key("from_name").String()
Smtp.EhloIdentity = sec.Key("ehlo_identity").String()
Smtp.SkipVerify = sec.Key("skip_verify").MustBool(false)
emails := Cfg.Section("emails")

View File

@ -20,18 +20,18 @@ func NewFakeExecutor(dsInfo *models.DataSource) (*FakeExecutor, error) {
}, nil
}
func (e *FakeExecutor) Query(ctx context.Context, dsInfo *models.DataSource, context *TsdbQuery) *BatchResult {
result := &BatchResult{QueryResults: make(map[string]*QueryResult)}
func (e *FakeExecutor) Query(ctx context.Context, dsInfo *models.DataSource, context *TsdbQuery) (*Response, error) {
result := &Response{Results: make(map[string]*QueryResult)}
for _, query := range context.Queries {
if results, has := e.results[query.RefId]; has {
result.QueryResults[query.RefId] = results
result.Results[query.RefId] = results
}
if testFunc, has := e.resultsFn[query.RefId]; has {
result.QueryResults[query.RefId] = testFunc(context)
result.Results[query.RefId] = testFunc(context)
}
}
return result
return result, nil
}
func (e *FakeExecutor) Return(refId string, series TimeSeriesSlice) {

View File

@ -37,8 +37,8 @@ func init() {
tsdb.RegisterTsdbQueryEndpoint("graphite", NewGraphiteExecutor)
}
func (e *GraphiteExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) *tsdb.BatchResult {
result := &tsdb.BatchResult{}
func (e *GraphiteExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
result := &tsdb.Response{}
from := "-" + formatTimeRange(tsdbQuery.TimeRange.From)
until := formatTimeRange(tsdbQuery.TimeRange.To)
@ -67,14 +67,12 @@ func (e *GraphiteExecutor) Query(ctx context.Context, dsInfo *models.DataSource,
req, err := e.createRequest(dsInfo, formData)
if err != nil {
result.Error = err
return result
return nil, err
}
httpClient, err := dsInfo.GetHttpClient()
if err != nil {
result.Error = err
return result
return nil, err
}
span, ctx := opentracing.StartSpanFromContext(ctx, "graphite query")
@ -90,17 +88,15 @@ func (e *GraphiteExecutor) Query(ctx context.Context, dsInfo *models.DataSource,
res, err := ctxhttp.Do(ctx, httpClient, req)
if err != nil {
result.Error = err
return result
return nil, err
}
data, err := e.parseResponse(res)
if err != nil {
result.Error = err
return result
return nil, err
}
result.QueryResults = make(map[string]*tsdb.QueryResult)
result.Results = make(map[string]*tsdb.QueryResult)
queryRes := tsdb.NewQueryResult()
for _, series := range data {
@ -114,8 +110,8 @@ func (e *GraphiteExecutor) Query(ctx context.Context, dsInfo *models.DataSource,
}
}
result.QueryResults["A"] = queryRes
return result
result.Results["A"] = queryRes
return result, nil
}
func (e *GraphiteExecutor) parseResponse(res *http.Response) ([]TargetResponseDTO, error) {

View File

@ -39,17 +39,17 @@ func init() {
tsdb.RegisterTsdbQueryEndpoint("influxdb", NewInfluxDBExecutor)
}
func (e *InfluxDBExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) *tsdb.BatchResult {
result := &tsdb.BatchResult{}
func (e *InfluxDBExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
result := &tsdb.Response{}
query, err := e.getQuery(dsInfo, tsdbQuery.Queries, tsdbQuery)
if err != nil {
return result.WithError(err)
return nil, err
}
rawQuery, err := query.Build(tsdbQuery)
if err != nil {
return result.WithError(err)
return nil, err
}
if setting.Env == setting.DEV {
@ -58,21 +58,21 @@ func (e *InfluxDBExecutor) Query(ctx context.Context, dsInfo *models.DataSource,
req, err := e.createRequest(dsInfo, rawQuery)
if err != nil {
return result.WithError(err)
return nil, err
}
httpClient, err := dsInfo.GetHttpClient()
if err != nil {
return result.WithError(err)
return nil, err
}
resp, err := ctxhttp.Do(ctx, httpClient, req)
if err != nil {
return result.WithError(err)
return nil, err
}
if resp.StatusCode/100 != 2 {
return result.WithError(fmt.Errorf("Influxdb returned statuscode invalid status code: %v", resp.Status))
return nil, fmt.Errorf("Influxdb returned statuscode invalid status code: %v", resp.Status)
}
var response Response
@ -82,17 +82,17 @@ func (e *InfluxDBExecutor) Query(ctx context.Context, dsInfo *models.DataSource,
err = dec.Decode(&response)
if err != nil {
return result.WithError(err)
return nil, err
}
if response.Err != nil {
return result.WithError(response.Err)
return nil, response.Err
}
result.QueryResults = make(map[string]*tsdb.QueryResult)
result.QueryResults["A"] = e.ResponseParser.Parse(&response, query)
result.Results = make(map[string]*tsdb.QueryResult)
result.Results["A"] = e.ResponseParser.Parse(&response, query)
return result
return result, nil
}
func (e *InfluxDBExecutor) getQuery(dsInfo *models.DataSource, queries []*tsdb.Query, context *tsdb.TsdbQuery) (*Query, error) {

View File

@ -22,24 +22,8 @@ type Query struct {
}
type Response struct {
BatchTimings []*BatchTiming `json:"timings"`
Results map[string]*QueryResult `json:"results"`
Message string `json:"message,omitempty"`
}
type BatchTiming struct {
TimeElapsed int64
}
type BatchResult struct {
Error error
QueryResults map[string]*QueryResult
Timings *BatchTiming
}
func (br *BatchResult) WithError(err error) *BatchResult {
br.Error = err
return br
Results map[string]*QueryResult `json:"results"`
Message string `json:"message,omitempty"`
}
type QueryResult struct {

View File

@ -85,9 +85,9 @@ func (e *MysqlExecutor) initEngine(dsInfo *models.DataSource) error {
return nil
}
func (e *MysqlExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) *tsdb.BatchResult {
result := &tsdb.BatchResult{
QueryResults: make(map[string]*tsdb.QueryResult),
func (e *MysqlExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
result := &tsdb.Response{
Results: make(map[string]*tsdb.QueryResult),
}
macroEngine := NewMysqlMacroEngine(tsdbQuery.TimeRange)
@ -102,7 +102,7 @@ func (e *MysqlExecutor) Query(ctx context.Context, dsInfo *models.DataSource, ts
}
queryResult := &tsdb.QueryResult{Meta: simplejson.New(), RefId: query.RefId}
result.QueryResults[query.RefId] = queryResult
result.Results[query.RefId] = queryResult
rawSql, err := macroEngine.Interpolate(rawSql)
if err != nil {
@ -138,7 +138,7 @@ func (e *MysqlExecutor) Query(ctx context.Context, dsInfo *models.DataSource, ts
}
}
return result
return result, nil
}
func (e MysqlExecutor) TransformToTable(query *tsdb.Query, rows *core.Rows, result *tsdb.QueryResult) error {

View File

@ -50,8 +50,8 @@ func init() {
tsdb.RegisterTsdbQueryEndpoint("opentsdb", NewOpenTsdbExecutor)
}
func (e *OpenTsdbExecutor) Query(ctx context.Context, dsInfo *models.DataSource, queryContext *tsdb.TsdbQuery) *tsdb.BatchResult {
result := &tsdb.BatchResult{}
func (e *OpenTsdbExecutor) Query(ctx context.Context, dsInfo *models.DataSource, queryContext *tsdb.TsdbQuery) (*tsdb.Response, error) {
result := &tsdb.Response{}
var tsdbQuery OpenTsdbQuery
@ -69,29 +69,26 @@ func (e *OpenTsdbExecutor) Query(ctx context.Context, dsInfo *models.DataSource,
req, err := e.createRequest(dsInfo, tsdbQuery)
if err != nil {
result.Error = err
return result
return nil, err
}
httpClient, err := dsInfo.GetHttpClient()
if err != nil {
result.Error = err
return result
return nil, err
}
res, err := ctxhttp.Do(ctx, httpClient, req)
if err != nil {
result.Error = err
return result
return nil, err
}
queryResult, err := e.parseResponse(tsdbQuery, res)
if err != nil {
return result.WithError(err)
return nil, err
}
result.QueryResults = queryResult
return result
result.Results = queryResult
return result, nil
}
func (e *OpenTsdbExecutor) createRequest(dsInfo *models.DataSource, data OpenTsdbQuery) (*http.Request, error) {

View File

@ -80,17 +80,17 @@ func (e *PrometheusExecutor) getClient(dsInfo *models.DataSource) (apiv1.API, er
return apiv1.NewAPI(client), nil
}
func (e *PrometheusExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) *tsdb.BatchResult {
result := &tsdb.BatchResult{}
func (e *PrometheusExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
result := &tsdb.Response{}
client, err := e.getClient(dsInfo)
if err != nil {
return result.WithError(err)
return nil, err
}
query, err := parseQuery(tsdbQuery.Queries, tsdbQuery)
if err != nil {
return result.WithError(err)
return nil, err
}
timeRange := apiv1.Range{
@ -108,15 +108,15 @@ func (e *PrometheusExecutor) Query(ctx context.Context, dsInfo *models.DataSourc
value, err := client.QueryRange(ctx, query.Expr, timeRange)
if err != nil {
return result.WithError(err)
return nil, err
}
queryResult, err := parseResponse(value, query)
if err != nil {
return result.WithError(err)
return nil, err
}
result.QueryResults = queryResult
return result
result.Results = queryResult
return result, nil
}
func formatLegend(metric model.Metric, query *PrometheusQuery) string {

View File

@ -8,7 +8,7 @@ import (
)
type TsdbQueryEndpoint interface {
Query(ctx context.Context, ds *models.DataSource, query *TsdbQuery) *BatchResult
Query(ctx context.Context, ds *models.DataSource, query *TsdbQuery) (*Response, error)
}
var registry map[string]GetTsdbQueryEndpointFn

View File

@ -2,25 +2,17 @@ package tsdb
import (
"context"
"github.com/grafana/grafana/pkg/models"
)
type HandleRequestFunc func(ctx context.Context, req *TsdbQuery) (*Response, error)
type HandleRequestFunc func(ctx context.Context, dsInfo *models.DataSource, req *TsdbQuery) (*Response, error)
func HandleRequest(ctx context.Context, req *TsdbQuery) (*Response, error) {
//TODO niceify
ds := req.Queries[0].DataSource
endpoint, err := getTsdbQueryEndpointFor(ds)
func HandleRequest(ctx context.Context, dsInfo *models.DataSource, req *TsdbQuery) (*Response, error) {
endpoint, err := getTsdbQueryEndpointFor(dsInfo)
if err != nil {
return nil, err
}
res := endpoint.Query(ctx, ds, req)
if res.Error != nil {
return nil, res.Error
}
return &Response{
Results: res.QueryResults,
BatchTimings: []*BatchTiming{res.Timings},
}, nil
return endpoint.Query(ctx, dsInfo, req)
}

View File

@ -29,6 +29,67 @@ func init() {
logger.Debug("Initializing TestData Scenario")
registerScenario(&Scenario{
Id: "exponential_heatmap_bucket_data",
Name: "Exponential heatmap bucket data",
Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
to := context.TimeRange.GetToAsMsEpoch()
var series []*tsdb.TimeSeries
start := 1
factor := 2
for i := 0; i < 10; i++ {
timeWalkerMs := context.TimeRange.GetFromAsMsEpoch()
serie := &tsdb.TimeSeries{Name: strconv.Itoa(start)}
start *= factor
points := make(tsdb.TimeSeriesPoints, 0)
for j := int64(0); j < 100 && timeWalkerMs < to; j++ {
v := float64(rand.Int63n(100))
points = append(points, tsdb.NewTimePoint(null.FloatFrom(v), float64(timeWalkerMs)))
timeWalkerMs += query.IntervalMs * 50
}
serie.Points = points
series = append(series, serie)
}
queryRes := tsdb.NewQueryResult()
queryRes.Series = append(queryRes.Series, series...)
return queryRes
},
})
registerScenario(&Scenario{
Id: "linear_heatmap_bucket_data",
Name: "Linear heatmap bucket data",
Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
to := context.TimeRange.GetToAsMsEpoch()
var series []*tsdb.TimeSeries
for i := 0; i < 10; i++ {
timeWalkerMs := context.TimeRange.GetFromAsMsEpoch()
serie := &tsdb.TimeSeries{Name: strconv.Itoa(i * 10)}
points := make(tsdb.TimeSeriesPoints, 0)
for j := int64(0); j < 100 && timeWalkerMs < to; j++ {
v := float64(rand.Int63n(100))
points = append(points, tsdb.NewTimePoint(null.FloatFrom(v), float64(timeWalkerMs)))
timeWalkerMs += query.IntervalMs * 50
}
serie.Points = points
series = append(series, serie)
}
queryRes := tsdb.NewQueryResult()
queryRes.Series = append(queryRes.Series, series...)
return queryRes
},
})
registerScenario(&Scenario{
Id: "random_walk",
Name: "Random Walk",

View File

@ -24,19 +24,19 @@ func init() {
tsdb.RegisterTsdbQueryEndpoint("grafana-testdata-datasource", NewTestDataExecutor)
}
func (e *TestDataExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) *tsdb.BatchResult {
result := &tsdb.BatchResult{}
result.QueryResults = make(map[string]*tsdb.QueryResult)
func (e *TestDataExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
result := &tsdb.Response{}
result.Results = make(map[string]*tsdb.QueryResult)
for _, query := range tsdbQuery.Queries {
scenarioId := query.Model.Get("scenarioId").MustString("random_walk")
if scenario, exist := ScenarioRegistry[scenarioId]; exist {
result.QueryResults[query.RefId] = scenario.Handler(query, tsdbQuery)
result.QueryResults[query.RefId].RefId = query.RefId
result.Results[query.RefId] = scenario.Handler(query, tsdbQuery)
result.Results[query.RefId].RefId = query.RefId
} else {
e.log.Error("Scenario not found", "scenarioId", scenarioId)
}
}
return result
return result, nil
}

View File

@ -19,7 +19,7 @@ func TestMetricQuery(t *testing.T) {
fakeExecutor := registerFakeExecutor()
fakeExecutor.Return("A", TimeSeriesSlice{&TimeSeries{Name: "argh"}})
res, err := HandleRequest(context.TODO(), req)
res, err := HandleRequest(context.TODO(), &models.DataSource{Id: 1, Type: "test"}, req)
So(err, ShouldBeNil)
Convey("Should return query results", func() {
@ -40,18 +40,13 @@ func TestMetricQuery(t *testing.T) {
fakeExecutor.Return("A", TimeSeriesSlice{&TimeSeries{Name: "argh"}})
fakeExecutor.Return("B", TimeSeriesSlice{&TimeSeries{Name: "barg"}})
res, err := HandleRequest(context.TODO(), req)
res, err := HandleRequest(context.TODO(), &models.DataSource{Id: 1, Type: "test"}, req)
So(err, ShouldBeNil)
Convey("Should return query results", func() {
So(len(res.Results), ShouldEqual, 2)
So(res.Results["B"].Series[0].Name, ShouldEqual, "barg")
})
Convey("Should have been batched in one request", func() {
So(len(res.BatchTimings), ShouldEqual, 1)
})
})
Convey("When query uses data source of unknown type", t, func() {
@ -61,7 +56,7 @@ func TestMetricQuery(t *testing.T) {
},
}
_, err := HandleRequest(context.TODO(), req)
_, err := HandleRequest(context.TODO(), &models.DataSource{Id: 12, Type: "testjughjgjg"}, req)
So(err, ShouldNotBeNil)
})
}

View File

@ -9,6 +9,8 @@ import 'angular-sanitize';
import 'angular-dragdrop';
import 'angular-bindonce';
import 'angular-ui';
import 'react';
import 'react-dom';
import 'ngreact';
import $ from 'jquery';

View File

@ -1,5 +1,4 @@
import * as React from 'react';
import 'react-dom';
import coreModule from '../core_module';
export interface IProps {

View File

@ -8,7 +8,6 @@ import "./directives/dropdown_typeahead";
import "./directives/metric_segment";
import "./directives/misc";
import "./directives/ng_model_on_blur";
import "./directives/password_strength";
import "./directives/spectrum_picker";
import "./directives/tags";
import "./directives/value_select_dropdown";

View File

@ -25,7 +25,7 @@ function (angular, require, coreModule, kbn) {
getText: '&clipboardButton'
},
link: function(scope, elem) {
require(['vendor/clipboard/dist/clipboard'], function(Clipboard) {
require(['clipboard'], function(Clipboard) {
scope.clipboard = new Clipboard(elem[0], {
text: function() {
return scope.getText();

View File

@ -1,45 +0,0 @@
define([
'../core_module',
],
function (coreModule) {
'use strict';
coreModule.default.directive('passwordStrength2', function() {
var template = '<div class="password-strength small" ng-if="!loginMode" ng-class="strengthClass">' +
'<em>{{strengthText}}</em>' +
'</div>';
return {
template: template,
scope: {
password: "=",
},
link: function($scope) {
$scope.strengthClass = '';
function passwordChanged(newValue) {
if (!newValue) {
$scope.strengthText = "";
$scope.strengthClass = "hidden";
return;
}
if (newValue.length < 4) {
$scope.strengthText = "strength: weak sauce.";
$scope.strengthClass = "password-strength-bad";
return;
}
if (newValue.length <= 8) {
$scope.strengthText = "strength: you can do better.";
$scope.strengthClass = "password-strength-ok";
return;
}
$scope.strengthText = "strength: strong like a bull.";
$scope.strengthClass = "password-strength-good";
}
$scope.$watch("password", passwordChanged);
}
};
});
});

View File

@ -106,6 +106,8 @@ function getStateDisplayModel(state) {
};
}
}
throw {message: 'Unknown alert state'};
}
function joinEvalMatches(matches, separator: string) {

View File

@ -84,7 +84,7 @@ export class AlertTabCtrl {
});
}
getNotificationIcon(type) {
getNotificationIcon(type): string {
switch (type) {
case "email": return "fa fa-envelope";
case "slack": return "fa fa-slack";
@ -95,6 +95,7 @@ export class AlertTabCtrl {
case "hipchat": return "fa fa-mail-forward";
case "pushover": return "fa fa-mobile";
}
return 'fa fa-bell';
}
getNotifications() {

View File

@ -11,7 +11,7 @@
<div>
<p class="share-modal-info-text">
Export the dashboard to a JSON file. The exporter will templatize the
dashboard's data sources to make it easy for other's to to import and reuse.
dashboard's data sources to make it easy for others to import and reuse.
You can share dashboards on <a class="external-link" href="https://grafana.com">Grafana.com</a>
</p>

View File

@ -69,10 +69,11 @@ export class QueryTroubleshooterCtrl {
}
}
getClipboardText() {
getClipboardText(): string {
if (this.jsonExplorer) {
return JSON.stringify(this.jsonExplorer.json, null, 2);
}
return '';
}
onRequestResponse(data) {

View File

@ -38,6 +38,8 @@ export class DashImportListCtrl {
});
}, 500);
});
} else {
return Promise.resolve();
}
});
}

View File

@ -80,6 +80,7 @@ export class PluginEditCtrl {
case 'app': return 'icon-gf icon-gf-apps';
case 'page': return 'icon-gf icon-gf-endpoint-tiny';
case 'dashboard': return 'icon-gf icon-gf-dashboard';
default: return 'icon-gf icon-gf-apps';
}
}

View File

@ -54,7 +54,7 @@ export class VariableEditorCtrl {
$scope.isValid = function() {
if (!$scope.ctrl.form.$valid) {
return;
return false;
}
if (!$scope.current.name.match(/^\w+$/)) {

View File

@ -352,12 +352,13 @@ export class InfluxQueryCtrl extends QueryCtrl {
this.panelCtrl.refresh();
}
getTagValueOperator(tagValue, tagOperator) {
getTagValueOperator(tagValue, tagOperator): string {
if (tagOperator !== '=~' && tagOperator !== '!~' && /^\/.*\/$/.test(tagValue)) {
return '=~';
} else if ((tagOperator === '=~' || tagOperator === '!~') && /^(?!\/.*\/$)/.test(tagValue)) {
return '=';
}
return null;
}
getCollapsedText() {

View File

@ -99,6 +99,7 @@ export class PrometheusDatasource {
var query: any = {};
query.expr = this.templateSrv.replace(target.expr, options.scopedVars, self.interpolateQueryExpr);
query.requestId = options.panelId + target.refId;
query.instant = target.instant;
var interval = this.templateSrv.replace(target.interval, options.scopedVars) || options.interval;
var intervalFactor = target.intervalFactor || 1;
@ -114,7 +115,11 @@ export class PrometheusDatasource {
}
var allQueryPromise = _.map(queries, query => {
return this.performTimeSeriesQuery(query, start, end);
if (!query.instant) {
return this.performTimeSeriesQuery(query, start, end);
} else {
return this.performInstantQuery(query, end);
}
});
return this.$q.all(allQueryPromise).then(responseList => {
@ -129,7 +134,11 @@ export class PrometheusDatasource {
result.push(self.transformMetricDataToTable(response.data.data.result));
} else {
for (let metricData of response.data.data.result) {
result.push(self.transformMetricData(metricData, activeTargets[index], start, end));
if (response.data.data.resultType === 'matrix') {
result.push(self.transformMetricData(metricData, activeTargets[index], start, end));
} else if (response.data.data.resultType === 'vector') {
result.push(self.transformInstantMetricData(metricData, activeTargets[index]));
}
}
}
});
@ -156,6 +165,11 @@ export class PrometheusDatasource {
return this._request('GET', url, query.requestId);
}
performInstantQuery(query, time) {
var url = '/api/v1/query?query=' + encodeURIComponent(query.expr) + '&time=' + time;
return this._request('GET', url, query.requestId);
}
performSuggestQuery(query, cache = false) {
var url = '/api/v1/label/__name__/values';
@ -317,6 +331,9 @@ export class PrometheusDatasource {
// Populate rows, set value to empty string when label not present.
_.each(md, function(series) {
if (series.value) {
series.values = [series.value];
}
if (series.values) {
for (i = 0; i < series.values.length; i++) {
var values = series.values[i];
@ -340,6 +357,13 @@ export class PrometheusDatasource {
return table;
}
transformInstantMetricData(md, options) {
var dps = [], metricLabel = null;
metricLabel = this.createMetricLabel(md.metric, options);
dps.push([parseFloat(md.value[1]), md.value[0] * 1000]);
return { target: metricLabel, datapoints: dps };
}
createMetricLabel(labelData, options) {
if (_.isUndefined(options) || _.isEmpty(options.legendFormat)) {
return this.getOriginalMetricName(labelData);

View File

@ -95,9 +95,7 @@ function (_) {
PrometheusMetricFindQuery.prototype.queryResultQuery = function(query) {
var end = this.datasource.getPrometheusTime(this.range.to, true);
var url = '/api/v1/query?query=' + encodeURIComponent(query) + '&time=' + end;
return this.datasource._request('GET', url)
return this.datasource.performInstantQuery({ expr: query }, end)
.then(function(result) {
return _.map(result.data.data.result, function(metricData) {
var text = metricData.metric.__name__ || '';

View File

@ -45,6 +45,8 @@
<div class="gf-form-select-wrapper width-8">
<select class="gf-form-input gf-size-auto" ng-model="ctrl.target.format" ng-options="f.value as f.text for f in ctrl.formats" ng-change="ctrl.refresh()"></select>
</div>
<gf-form-switch class="gf-form" label="Instant" label-class="width-5" checked="ctrl.target.instant" on-change="ctrl.refresh()">
</gf-form-switch>
<label class="gf-form-label">
<a href="{{ctrl.linkToPrometheus}}" target="_blank" bs-tooltip="'Link to Graph in Prometheus'">
<i class="fa fa-share-square-o"></i>

View File

@ -12,6 +12,7 @@ class PrometheusQueryCtrl extends QueryCtrl {
metric: any;
resolutions: any;
formats: any;
instant: any;
oldTarget: any;
suggestMetrics: any;
getMetricsAutocomplete: any;
@ -36,6 +37,8 @@ class PrometheusQueryCtrl extends QueryCtrl {
{text: 'Table', value: 'table'},
];
this.instant = false;
this.updateLink();
}

View File

@ -26,7 +26,7 @@ describe('PrometheusDatasource', function() {
'&start=1443438675&end=1443460275&step=60';
var query = {
range: { from: moment(1443438674760), to: moment(1443460274760) },
targets: [{ expr: 'test{job="testjob"}' }],
targets: [{ expr: 'test{job="testjob"}', format: 'time_series' }],
interval: '60s'
};
var response = {
@ -62,7 +62,7 @@ describe('PrometheusDatasource', function() {
'&start=' + start + '&end=' + end + '&step=' + step;
var query = {
range: { from: moment(1443438674760), to: moment(1443460274760) },
targets: [{ expr: 'test{job="testjob"}' }],
targets: [{ expr: 'test{job="testjob"}', format: 'time_series' }],
interval: '60s'
};
var response = {
@ -119,7 +119,40 @@ describe('PrometheusDatasource', function() {
expect(results.data[1].datapoints[3][0]).to.be(null);
});
});
describe('When performing annotationQuery', function() {
describe('When querying prometheus with one target and instant = true', function () {
var results;
var urlExpected = 'proxied/api/v1/query?query=' +
encodeURIComponent('test{job="testjob"}') +
'&time=1443460275';
var query = {
range: { from: moment(1443438674760), to: moment(1443460274760) },
targets: [{ expr: 'test{job="testjob"}', format: 'time_series', instant: true }],
interval: '60s'
};
var response = {
status: "success",
data: {
resultType: "vector",
result: [{
metric: { "__name__": "test", job: "testjob" },
value: [1443454528, "3846"]
}]
}
};
beforeEach(function () {
ctx.$httpBackend.expect('GET', urlExpected).respond(response);
ctx.ds.query(query).then(function (data) { results = data; });
ctx.$httpBackend.flush();
});
it('should generate the correct query', function () {
ctx.$httpBackend.verifyNoOutstandingExpectation();
});
it('should return series list', function () {
expect(results.data.length).to.be(1);
expect(results.data[0].target).to.be('test{job="testjob"}');
});
});
describe('When performing annotationQuery', function () {
var results;
var urlExpected = 'proxied/api/v1/query_range?query=' +
encodeURIComponent('ALERTS{alertstate="firing"}') +
@ -195,4 +228,45 @@ describe('PrometheusDatasource', function() {
);
});
});
describe('When resultFormat is table and instant = true', function() {
var results;
var urlExpected = 'proxied/api/v1/query?query=' +
encodeURIComponent('test{job="testjob"}') +
'&time=1443460275';
var query = {
range: { from: moment(1443438674760), to: moment(1443460274760) },
targets: [{ expr: 'test{job="testjob"}', format: 'time_series', instant: true }],
interval: '60s'
};
var response = {
status: "success",
data: {
resultType: "vector",
result: [{
metric: { "__name__": "test", job: "testjob" },
value: [1443454528, "3846"]
}]
}
};
beforeEach(function () {
ctx.$httpBackend.expect('GET', urlExpected).respond(response);
ctx.ds.query(query).then(function (data) { results = data; });
ctx.$httpBackend.flush();
});
it('should return table model', function() {
var table = ctx.ds.transformMetricDataToTable(response.data.result);
expect(table.type).to.be('table');
expect(table.rows).to.eql(
[
[ 1443454528000, 'test', 'testjob', 3846]
]);
expect(table.columns).to.eql(
[ { text: 'Time', type: 'time' },
{ text: '__name__' },
{ text: 'job' },
{ text: 'Value' }
]
);
});
});
});

View File

@ -142,34 +142,34 @@ export class DataProcessor {
let fields = [];
var firstItem = dataList[0];
let fieldParts = [];
function getPropertiesRecursive(obj) {
_.forEach(obj, (value, key) => {
if (_.isObject(value)) {
fieldParts.push(key);
getPropertiesRecursive(value);
} else {
if (!onlyNumbers || _.isNumber(value)) {
let field = fieldParts.concat(key).join('.');
fields.push(field);
}
_.forEach(obj, (value, key) => {
if (_.isObject(value)) {
fieldParts.push(key);
getPropertiesRecursive(value);
} else {
if (!onlyNumbers || _.isNumber(value)) {
let field = fieldParts.concat(key).join('.');
fields.push(field);
}
});
fieldParts.pop();
}
});
fieldParts.pop();
}
if (firstItem.type === 'docs') {
if (firstItem.datapoints.length === 0) {
return [];
}
getPropertiesRecursive(firstItem.datapoints[0]);
return fields;
}
return fields;
}
getXAxisValueOptions(options) {
switch (this.panel.xaxis.mode) {
case 'time': {
return [];
}
case 'series': {
return [
{text: 'Avg', value: 'avg'},
@ -180,6 +180,8 @@ export class DataProcessor {
];
}
}
return [];
}
pluckDeep(obj: any, property: string) {

View File

@ -120,6 +120,8 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) {
if (panelWidth === 0) {
return true;
}
return false;
}
function drawHook(plot) {
@ -385,6 +387,7 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) {
if (legendSideLastValue !== null && panel.legend.rightSide !== legendSideLastValue) {
return true;
}
return false;
}
function addTimeAxis(options) {

View File

@ -21,7 +21,7 @@ function elasticHistogramToHeatmap(seriesList) {
for (let series of seriesList) {
let bound = Number(series.alias);
if (isNaN(bound)) {
return;
return heatmap;
}
for (let point of series.datapoints) {
@ -384,36 +384,40 @@ function isHeatmapDataEqual(objA: any, objB: any): boolean {
let is_eql = !emptyXOR(objA, objB);
_.forEach(objA, (xBucket: XBucket, x) => {
if (objB[x]) {
if (objB[x]) {
if (emptyXOR(xBucket.buckets, objB[x].buckets)) {
is_eql = false;
return false;
}
_.forEach(xBucket.buckets, (yBucket: YBucket, y) => {
if (objB[x].buckets && objB[x].buckets[y]) {
if (objB[x].buckets[y].values) {
is_eql = _.isEqual(_.sortBy(yBucket.values), _.sortBy(objB[x].buckets[y].values));
if (!is_eql) {
return false;
}
} else {
is_eql = false;
return false;
}
} else {
is_eql = false;
return false;
}
});
if (!is_eql) {
return false;
}
} else {
is_eql = false;
return false;
}
_.forEach(xBucket.buckets, (yBucket: YBucket, y) => {
if (objB[x].buckets && objB[x].buckets[y]) {
if (objB[x].buckets[y].values) {
is_eql = _.isEqual(_.sortBy(yBucket.values), _.sortBy(objB[x].buckets[y].values));
if (!is_eql) {
return false;
} else {
return true;
}
} else {
is_eql = false;
return false;
}
} else {
is_eql = false;
return false;
}
});
if (!is_eql) {
return false;
} else {
return true;
}
} else {
is_eql = false;
return false;
}
});
return is_eql;
@ -425,11 +429,11 @@ function emptyXOR(foo: any, bar: any): boolean {
export {
convertToHeatMap,
elasticHistogramToHeatmap,
convertToCards,
mergeZeroBuckets,
getMinLog,
getValueBucketBound,
isHeatmapDataEqual,
calculateBucketSize
elasticHistogramToHeatmap,
convertToCards,
mergeZeroBuckets,
getMinLog,
getValueBucketBound,
isHeatmapDataEqual,
calculateBucketSize
};

View File

@ -137,6 +137,7 @@ transformers['table'] = {
if (!data || data.length === 0) {
return [];
}
return data[0].columns;
},
transform: function(data, panel, model) {
if (!data || data.length === 0) {

View File

@ -15,14 +15,14 @@ System.config({
"jquery": "vendor/npm/jquery/dist/jquery.js",
'lodash-src': 'vendor/npm/lodash/lodash.js',
"lodash": 'app/core/lodash_extended.js',
"angular": "vendor/angular/angular.js",
"angular": "vendor/npm/angular/angular.js",
"bootstrap": "vendor/bootstrap/bootstrap.js",
'angular-route': 'vendor/angular-route/angular-route.js',
'angular-sanitize': 'vendor/angular-sanitize/angular-sanitize.js',
'angular-route': 'vendor/npm/angular-route/angular-route.js',
'angular-sanitize': 'vendor/npm/angular-sanitize/angular-sanitize.js',
"angular-ui": "vendor/angular-ui/ui-bootstrap-tpls.js",
"angular-strap": "vendor/angular-other/angular-strap.js",
"angular-dragdrop": "vendor/angular-native-dragdrop/draganddrop.js",
"angular-bindonce": "vendor/angular-bindonce/bindonce.js",
"angular-dragdrop": "vendor/npm/angular-native-dragdrop/draganddrop.js",
"angular-bindonce": "vendor/npm/angular-bindonce/bindonce.js",
"spectrum": "vendor/spectrum.js",
"bootstrap-tagsinput": "vendor/tagsinput/bootstrap-tagsinput.js",
"jquery.flot": "vendor/flot/jquery.flot",
@ -40,6 +40,7 @@ System.config({
"gridstack": "vendor/npm/gridstack/dist/gridstack.js",
"gridstack.jquery-ui": "vendor/npm/gridstack/dist/gridstack.jQueryUI.js",
"ace": "vendor/npm/ace-builds/src-noconflict/ace"
"clipboard": "vendor/npm/clipboard/dist/clipboard.js"
},
packages: {
@ -75,7 +76,7 @@ System.config({
format: 'global',
deps: ['gridstack'],
},
'vendor/angular/angular.js': {
'vendor/npm/angular/angular.js': {
format: 'global',
deps: ['jquery'],
exports: 'angular',

View File

@ -64,6 +64,38 @@
.navbar-page-btn-wrapper {
float: left;
<<<<<<< HEAD
||||||| merged common ancestors
margin: 0;
border-right: 1px solid $tight-form-border;
background-color: $navbarButtonBackground;
padding: 0.4rem 1.0rem 0.4rem 1rem;
min-height:: $navbarHeight;
.fa-caret-down {
font-size: 70%;
}
.fa-chevron-left{
display: none;
}
=======
margin: 0;
border-right: 1px solid $tight-form-border;
background-color: $navbarButtonBackground;
padding: 0.4rem 1.0rem 0.4rem 1rem;
min-height: $navbarHeight;
.fa-caret-down {
font-size: 70%;
}
.fa-chevron-left{
display: none;
}
>>>>>>> 0e5e2f3fb990293632402b20107aa09999c3f844
&:hover {
background: $navbarButtonBackgroundHighlight;
}
@ -78,7 +110,7 @@
color: darken($link-color, 5%);
font-size: $font-size-lg;
padding: 1rem 1rem 0.75rem 1rem;
min-height:: $navbarHeight;
min-height: $navbarHeight;
.fa-caret-down {
font-size: 60%;

View File

@ -23,15 +23,15 @@
"jquery": "vendor/npm/jquery/dist/jquery.js",
'lodash-src': 'vendor/npm/lodash/lodash.js',
"lodash": 'app/core/lodash_extended.js',
"angular": 'vendor/angular/angular.js',
'angular-mocks': 'vendor/angular-mocks/angular-mocks.js',
"angular": 'vendor/npm/angular/angular.js',
'angular-mocks': 'vendor/npm/angular-mocks/angular-mocks.js',
"bootstrap": "vendor/bootstrap/bootstrap.js",
'angular-route': 'vendor/angular-route/angular-route.js',
'angular-sanitize': 'vendor/angular-sanitize/angular-sanitize.js',
'angular-route': 'vendor/npm/angular-route/angular-route.js',
'angular-sanitize': 'vendor/npm/angular-sanitize/angular-sanitize.js',
"angular-ui": "vendor/angular-ui/ui-bootstrap-tpls.js",
"angular-strap": "vendor/angular-other/angular-strap.js",
"angular-dragdrop": "vendor/angular-native-dragdrop/draganddrop.js",
"angular-bindonce": "vendor/angular-bindonce/bindonce.js",
"angular-dragdrop": "vendor/npm/angular-native-dragdrop/draganddrop.js",
"angular-bindonce": "vendor/npm/angular-bindonce/bindonce.js",
"spectrum": "vendor/spectrum.js",
"bootstrap-tagsinput": "vendor/tagsinput/bootstrap-tagsinput.js",
"jquery.flot": "vendor/flot/jquery.flot",
@ -49,6 +49,7 @@
"gridstack": "vendor/npm/gridstack/dist/gridstack.js",
"gridstack.jquery-ui": "vendor/npm/gridstack/dist/gridstack.jQueryUI.js",
"ace": "vendor/npm/ace-builds/src-noconflict/ace",
"clipboard": "vendor/npm/clipboard/dist/clipboard.js"
},
packages: {
@ -64,6 +65,7 @@
},
meta: {
<<<<<<< HEAD
'vendor/npm/jquery-ui/jquery-ui.js': {
format: 'amd',
deps: ['jquery'],
@ -77,11 +79,16 @@
deps: ['gridstack'],
},
'vendor/angular/angular.js': {
||||||| merged common ancestors
'vendor/angular/angular.js': {
=======
'vendor/npm/angular/angular.js': {
>>>>>>> 0e5e2f3fb990293632402b20107aa09999c3f844
format: 'global',
deps: ['jquery'],
exports: 'angular',
},
'vendor/angular-mocks/angular-mocks.js': {
'vendor/npm/angular-mocks/angular-mocks.js': {
format: 'global',
deps: ['angular'],
},

View File

@ -1,36 +0,0 @@
{
"name": "angular-bindonce",
"version": "0.3.3",
"main": "bindonce.js",
"description": "Zero watchers binding directives for AngularJS",
"homepage": "https://github.com/Pasvaz/bindonce",
"author": "Pasquale Vazzana <pasqualevazzana@gmail.com>",
"repository": {
"type": "git",
"url": "https://github.com/Pasvaz/bindonce.git"
},
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"components"
],
"dependencies": {},
"keywords": [
"angularjs",
"angular",
"directive",
"binding",
"watcher",
"bindonce"
],
"_release": "0.3.3",
"_resolution": {
"type": "version",
"tag": "0.3.3",
"commit": "0fcf71e6effc88179893c9c06baf6c6bf9037632"
},
"_source": "https://github.com/Pasvaz/bindonce.git",
"_target": "0.3.3",
"_originalSource": "angular-bindonce"
}

View File

@ -1,43 +0,0 @@
# 0.3.3 (2014-02-12)
### Features
- **bo-disabled:**
- Add support for ng-disabled/bo-disabled #110
<hr />
# 0.3.2 (2014-11-23)
### Bug Fixes
- **Angular 1.3 compatibility**
<hr />
# 0.3.1 (2014-02-12)
### Features
- **bo-bind:**
- alias for bo-text
### Bug Fixes
- **Angular Promises**
- Ensures that promises are resolved before to run binders ([b3ef1b4](https://github.com/Pasvaz/bindonce/commit/b3ef1b46edfe83f10ed455d5520027f731563f32))
### Minor improvements
- Updated Readme
<hr />
# 0.3.0 (2014-01-21)
### Features
- **bo-switch:**
- Create new directive: bo-switch ([652d0db](https://github.com/Pasvaz/bindonce/commit/652d0db04325166a180377c738a376543b5f2357))
<hr />
# 0.2.3 (2014-01-20)
### Bug Fixes
- **bo-if:**
- Ensures that we both process newly added binders from bo-if, and that
we only process each binder once ([d11f863](https://github.com/Pasvaz/bindonce/commit/e091c273bbd17603d410fecc363874f0d1e6f38e))
### Features
- **Minification:**
- add min file ([47277ee](https://github.com/Pasvaz/bindonce/commit/47277eedd092b3210de362c725a7dadcddac8e87))
- **Changelog:**
- Created a changelog file

View File

@ -1,154 +0,0 @@
Bindonce
========
High performance binding for AngularJs
## Usage
* download, clone or fork it or install it using [bower](http://twitter.github.com/bower/) `bower install angular-bindonce`
* Include the `bindonce.js` script provided by this component into your app.
* Add `'pasvaz.bindonce'` as a module dependency to your app: `angular.module('app', ['pasvaz.bindonce'])`
## Demo
Here is an example of how AngularJs can [freeze your UI](http://plnkr.co/edit/jwrHVb?p=preview), try to press and hold a key inside the input field, when the table is filled with only 1 person everything is ok, you can see how the DOM is updated by the input in real time, however if you try to load 1000 person *(or even 500 if the testing device is not powerfull)* and repeat the experiment you can see how the UI is frozen. In [this other demo](http://plnkr.co/edit/0DGOrk?p=preview) BindOnce will take care of your watchers and the UI will be reactive as it should be. The code is the same for both demos, the only difference is that I replaced any `ng-*` tag inside the table with the equivalent `bo-*` tag.
* [AngularJs regular Demo](http://plnkr.co/edit/jwrHVb?p=preview)
* [Demo with Bindonce](http://plnkr.co/edit/0DGOrk?p=preview)
## Overview
AngularJs provides a great data binding system but if you abuse of it the page can run into some performance issues, it's known that more of 2000 watchers can lag the UI and that amount can be reached easily if you don't pay attention to the data-binding. Sometime you really need to bind your data using watchers, especially for SPA because the data are updated in real time, but often you can avoid it with some efforts, most of the data presented in your page, once rendered, are immutable so you shouldn't keep watching them for changes.
For instance, take a look to this snippet:
```html
<ul>
<li ng-repeat="person in Persons">
<a ng-href="#/people/{{person.id}}"><img ng-src="{{person.imageUrl}}"></a>
<a ng-href="#/people/{{person.id}}"><span ng-bind="person.name"></span></a>
<p ng-class="{'cycled':person.generated}" ng-bind-html-unsafe="person.description"></p>
</li>
</ul>
```
Angular internally creates a `$watch` for each `ng-*` directive in order to keep the data up to date, so in this example just for displaying few info it creates 6 + 1 *(ngRepeatWatch)* watchers per `person`, even if the `person` is supposed to remain the same once shown. Iterate this amount for each person and you can have an idea about how easy is to reach 2000 watchers. Now if you need it because those data could change while you show the page or are bound to some models, it's ok. But most of the time they are static data that don't change once rendered. This is where **bindonce** can really help you.
The above example done with **bindonce**:
```html
<ul>
<li bindonce ng-repeat="person in Persons">
<a bo-href="'#/people/' + person.id"><img bo-src="person.imageUrl"></a>
<a bo-href="'#/people/' + person.id" bo-text="person.name"></a>
<p bo-class="{'cycled':person.generated}" bo-html="person.description"></p>
</li>
</ul>
```
Now this example uses **0 watches** per `person` and renders exactly the same result as the above that uses ng-*. *(Angular still uses 1 watcher for ngRepeatWatch)*
### The smart approach
OK until here nothing completely new, with a bit of efforts you could create your own directive and render the `person` inside the `link` function, or you could use [watch fighters](https://github.com/abourget/abourget-angular) that has a similar approach, but there is still one problem that you have to face and **bindonce** already handles it: *the existence of the data when the directive renders the content*. Usually the directives, unless you use watchers or bind their attributes to the scope (still a watcher), render the content when they are loaded into the markup, but if at that given time your data is not available, the directive can't render it. Bindonce can wait until the data is ready before to rendering the content.
Let's take a look at the follow snippet to better understand the concept:
```html
<span my-custom-set-text="Person.firstname"></span>
<span my-custom-set-text="Person.lastname"></span>
...
<script>
angular.module('testApp', [])
.directive('myCustomSetText', function () {
return {
link:function (scope, elem, attr, ctrl) {
elem.text(scope.$eval(attr.myCustomSetText));
}
}
});
</script>
```
This basic directive works as expected, it renders the `Person` data without using any watchers. However, if `Person` is not yet available inside the $scope when the page is loaded (say we get `Person` via $http or via $resource), the directive is useless, `scope.$eval(attr.myCustomSetText)` simply renders nothing and exits.
Here is how we can solve this issue with **bindonce**:
```html
<div bindonce="Person" bo-title="Person.title">
<span bo-text="Person.firstname"></span>
<span bo-text="Person.lastname"></span>
<img bo-src="Person.picture" bo-alt="Person.title">
<p bo-class="{'fancy':Person.isNice}" bo-html="Person.story"></p>
</div>
```
`bindonce="Person"` does the trick, any `bo-*` attribute belonging to `bindonce` waits until the parent `bindonce="{somedata}"` is validated and then renders its content. Once the scope contains the value `Person` then each bo-* child gets filled with the proper values. In order to accomplish this task, **bindonce** uses just **one** temporary watcher, no matters how many children need to be rendered. As soon as it gets `Person` the watcher is promptly removed. If the $scope already contains the data `bindonce` is looking for, then it doesn't create the temporary watcher and simply starts rendering its children.
You may have noticed that the first example didn't assign any value to the `bindonce` attribute:
```html
<ul>
<li bindonce ng-repeat="person in Persons">
...
```
when used with `ng-repeat` `bindonce` doesn't need to check if `person` is defined because `ng-repeat` creates the directives only when `person` exists. You could be more explicit: `<li bindonce="person" ng-repeat="person in Persons">`, however assigning a value to `bindonce` in an `ng-repeat` won't make any difference.
### Interpolation
Some directives (ng-href, ng-src) use interpolation, ie: `ng-href="/profile/{{User.profileId}}"`.
Both `ng-href` and `ng-src` have the bo-* equivalent directives: `bo-href-i` and `bo-src-i` (pay attention to the **-i**, it stands for **interpolate**). As expected they don't use watchers however Angular creates one watcher per interpolation, for instance `bo-href-i="/profile/{{User.profileId}}"` sets the element's href **once**, as expected, but Angular keeps a watcher active on `{{User.profileId}}` even if `bo-href-i` doesn't use it.
That's why by default the `bo-href` doesn't use interpolation or watchers. The above equivalent with 0 watchers would be `bo-href="'/profile/' + User.profileId"`. Nevertheless, `bo-href-i` and `bo-src-i` are still maintained for compatibility reasons.
### Filters
Almost every `bo-*` directive replace the equivalent `ng-*` and works in the same ways, except it is evaluated once.
Consequentially you can use any valid angular expression, including filters. This is an example how to use a filter:
```html
<div bindonce="Person">
<span bo-bind="Person.bill | currency:'USD$'"></span>
</div>
```
## Attribute Usage
| Directive | Description | Example |
|------------|----------------|-----|
| `bindonce="{somedata}"`| **bindonce** is the main directive. `{somedata}` is optional, and if present, forces bindonce to wait until `somedata` is defined before rendering its children | `<div bindonce="Person">...<div>` |
| `bo-if = "condition"` | equivalent to `ng-if` but doesn't use watchers |`<ANY bo-if="Person.isPublic"></ANY>`|
| `bo-switch = "expression"` | equivalent to `ng-switch` but doesn't use watchers |`<div bo-switch="Person.isPublic">` `<span bo-switch-when="'yes">public</span>` `<span bo-switch-default>private</span>` `</div>`|
| `bo-show = "condition"` | equivalent to `ng-show` but doesn't use watchers |`<ANY bo-show="Person.isPublic"></ANY>`|
| `bo-hide = "condition"` | equivalent to `ng-hide` but doesn't use watchers |`<ANY bo-hide="Person.isPrivate"></ANY>`|
| `bo-disabled = "condition"` | equivalent to `ng-disabled` but doesn't use watchers |`<ANY bo-disabled="Person.isUnavailable"></ANY>`|
| `bo-text = "text"` | evaluates "text" and print it as text inside the element | `<span bo-text="Person.name"></span>` |
| `bo-bind = "text"` | alias for `bo-text`, equivalent to `ng-bind` but doesn't use watchers | `<span bo-bind="Person.name"></span>` |
| `bo-html = "markup"` | evaluates "markup" and render it as html inside the element |`bo-html="Person.description"`|
| `bo-href-i = "url"`<br>*use `bo-href` instead* | **equivalent** to `ng-href`.<br>**Heads up!** Using interpolation `{{}}` it creates one watcher: <br>`bo-href-i="/p/{{Person.id}}"`. <br>Use `bo-href` to avoid the watcher:<br> `bo-href="'/p/' + Person.id"` |`<a bo-href-i="/profile{{Person.id}}"></a>`|
| `bo-href = "url"` | **similar** to `ng-href` but doesn't allow interpolation using `{{}}` like `ng-href`. <br>**Heads up!** You can't use interpolation `{{}}` inside the url, use bo-href-i for that purpose |`<a bo-href="'/profile' + Person.id"></a>` <br />or<br /> `<a bo-href="link" bo-text="Link"></a>`|
| `bo-src-i = "url"`<br>*use `bo-src` instead* | **equivalent** to `ng-src`. <br>**Heads up!** It creates one watcher |`<img bo-src-i="{{picture}}" bo-alt="title">`|
| `bo-src = "url"` | **similar** to `ng-src` but doesn't allow interpolation using `{{}}` like `ng-src`. <br>**Heads up!** You can't use interpolation `{{}}`, use bo-src-i for that purpose |`<img bo-src="picture" bo-alt="title">`|
| `bo-class = "object/string"` | equivalent to `ng-class` but doesn't use watchers |`<span bo-class="{'fancy':Person.condition}">`|
| `bo-alt = "text"` | evaluates "text" and render it as `alt` for the element |`<ANY bo-alt="title">`|
| `bo-title = "text"` | evaluates "text" and render it as `title` for the element |`<ANY bo-title="title">`|
| `bo-id = "#id"` | evaluates "#id" and render it as `id` for the element |`<ANY bo-id="id">`|
| `bo-style = "object"` | equivalent to `ng-style` but doesn't use watchers |`<ANY bo-style="{'color':Person.color}">`|
| `bo-value = "expression"` | evaluates "expression" and render it as `value` for the element |`<input type="radio" bo-value="value">`|
| `bo-attr bo-attr-foo = "text"` | evaluates "text" and render it as a custom attribute for the element |`<div bo-attr bo-attr-foo="bar"></div>`|
## Build
```
$ npm install uglify-js -g
$ uglifyjs bindonce.js -c -m -o bindonce.min.js
```
## Todo
Tests
## Copyright
BindOnce was written by **Pasquale Vazzana**, you can follow him on [google+](https://plus.google.com/101872882413388363602) or on [@twitter](https://twitter.com/PasqualeVazzana)
Thanks to all the [contributors](https://github.com/Pasvaz/bindonce/graphs/contributors)
## LICENSE - "MIT License"
Copyright (c) 2013-2014 Pasquale Vazzana
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,325 +0,0 @@
(function () {
"use strict";
/**
* Bindonce - Zero watches binding for AngularJs
* @version v0.3.3
* @link https://github.com/Pasvaz/bindonce
* @author Pasquale Vazzana <pasqualevazzana@gmail.com>
* @license MIT License, http://www.opensource.org/licenses/MIT
*/
var bindonceModule = angular.module('pasvaz.bindonce', []);
bindonceModule.directive('bindonce', function ()
{
var toBoolean = function (value)
{
if (value && value.length !== 0)
{
var v = angular.lowercase("" + value);
value = !(v === 'f' || v === '0' || v === 'false' || v === 'no' || v === 'n' || v === '[]');
}
else
{
value = false;
}
return value;
};
var msie = parseInt((/msie (\d+)/.exec(angular.lowercase(navigator.userAgent)) || [])[1], 10);
if (isNaN(msie))
{
msie = parseInt((/trident\/.*; rv:(\d+)/.exec(angular.lowercase(navigator.userAgent)) || [])[1], 10);
}
var bindonceDirective =
{
restrict: "AM",
controller: ['$scope', '$element', '$attrs', '$interpolate', function ($scope, $element, $attrs, $interpolate)
{
var showHideBinder = function (elm, attr, value)
{
var show = (attr === 'show') ? '' : 'none';
var hide = (attr === 'hide') ? '' : 'none';
elm.css('display', toBoolean(value) ? show : hide);
};
var classBinder = function (elm, value)
{
if (angular.isObject(value) && !angular.isArray(value))
{
var results = [];
angular.forEach(value, function (value, index)
{
if (value) results.push(index);
});
value = results;
}
if (value)
{
elm.addClass(angular.isArray(value) ? value.join(' ') : value);
}
};
var transclude = function (transcluder, scope)
{
transcluder.transclude(scope, function (clone)
{
var parent = transcluder.element.parent();
var afterNode = transcluder.element && transcluder.element[transcluder.element.length - 1];
var parentNode = parent && parent[0] || afterNode && afterNode.parentNode;
var afterNextSibling = (afterNode && afterNode.nextSibling) || null;
angular.forEach(clone, function (node)
{
parentNode.insertBefore(node, afterNextSibling);
});
});
};
var ctrl =
{
watcherRemover: undefined,
binders: [],
group: $attrs.boName,
element: $element,
ran: false,
addBinder: function (binder)
{
this.binders.push(binder);
// In case of late binding (when using the directive bo-name/bo-parent)
// it happens only when you use nested bindonce, if the bo-children
// are not dom children the linking can follow another order
if (this.ran)
{
this.runBinders();
}
},
setupWatcher: function (bindonceValue)
{
var that = this;
this.watcherRemover = $scope.$watch(bindonceValue, function (newValue)
{
if (newValue === undefined) return;
that.removeWatcher();
that.checkBindonce(newValue);
}, true);
},
checkBindonce: function (value)
{
var that = this, promise = (value.$promise) ? value.$promise.then : value.then;
// since Angular 1.2 promises are no longer
// undefined until they don't get resolved
if (typeof promise === 'function')
{
promise(function ()
{
that.runBinders();
});
}
else
{
that.runBinders();
}
},
removeWatcher: function ()
{
if (this.watcherRemover !== undefined)
{
this.watcherRemover();
this.watcherRemover = undefined;
}
},
runBinders: function ()
{
while (this.binders.length > 0)
{
var binder = this.binders.shift();
if (this.group && this.group != binder.group) continue;
var value = binder.scope.$eval((binder.interpolate) ? $interpolate(binder.value) : binder.value);
switch (binder.attr)
{
case 'boIf':
if (toBoolean(value))
{
transclude(binder, binder.scope.$new());
}
break;
case 'boSwitch':
var selectedTranscludes, switchCtrl = binder.controller[0];
if ((selectedTranscludes = switchCtrl.cases['!' + value] || switchCtrl.cases['?']))
{
binder.scope.$eval(binder.attrs.change);
angular.forEach(selectedTranscludes, function (selectedTransclude)
{
transclude(selectedTransclude, binder.scope.$new());
});
}
break;
case 'boSwitchWhen':
var ctrl = binder.controller[0];
ctrl.cases['!' + binder.attrs.boSwitchWhen] = (ctrl.cases['!' + binder.attrs.boSwitchWhen] || []);
ctrl.cases['!' + binder.attrs.boSwitchWhen].push({ transclude: binder.transclude, element: binder.element });
break;
case 'boSwitchDefault':
var ctrl = binder.controller[0];
ctrl.cases['?'] = (ctrl.cases['?'] || []);
ctrl.cases['?'].push({ transclude: binder.transclude, element: binder.element });
break;
case 'hide':
case 'show':
showHideBinder(binder.element, binder.attr, value);
break;
case 'class':
classBinder(binder.element, value);
break;
case 'text':
binder.element.text(value);
break;
case 'html':
binder.element.html(value);
break;
case 'style':
binder.element.css(value);
break;
case 'disabled':
binder.element.prop('disabled', value);
break;
case 'src':
binder.element.attr(binder.attr, value);
if (msie) binder.element.prop('src', value);
break;
case 'attr':
angular.forEach(binder.attrs, function (attrValue, attrKey)
{
var newAttr, newValue;
if (attrKey.match(/^boAttr./) && binder.attrs[attrKey])
{
newAttr = attrKey.replace(/^boAttr/, '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
newValue = binder.scope.$eval(binder.attrs[attrKey]);
binder.element.attr(newAttr, newValue);
}
});
break;
case 'href':
case 'alt':
case 'title':
case 'id':
case 'value':
binder.element.attr(binder.attr, value);
break;
}
}
this.ran = true;
}
};
angular.extend(this, ctrl);
}],
link: function (scope, elm, attrs, bindonceController)
{
var value = attrs.bindonce && scope.$eval(attrs.bindonce);
if (value !== undefined)
{
bindonceController.checkBindonce(value);
}
else
{
bindonceController.setupWatcher(attrs.bindonce);
elm.bind("$destroy", bindonceController.removeWatcher);
}
}
};
return bindonceDirective;
});
angular.forEach(
[
{ directiveName: 'boShow', attribute: 'show' },
{ directiveName: 'boHide', attribute: 'hide' },
{ directiveName: 'boClass', attribute: 'class' },
{ directiveName: 'boText', attribute: 'text' },
{ directiveName: 'boBind', attribute: 'text' },
{ directiveName: 'boHtml', attribute: 'html' },
{ directiveName: 'boSrcI', attribute: 'src', interpolate: true },
{ directiveName: 'boSrc', attribute: 'src' },
{ directiveName: 'boHrefI', attribute: 'href', interpolate: true },
{ directiveName: 'boHref', attribute: 'href' },
{ directiveName: 'boAlt', attribute: 'alt' },
{ directiveName: 'boTitle', attribute: 'title' },
{ directiveName: 'boId', attribute: 'id' },
{ directiveName: 'boStyle', attribute: 'style' },
{ directiveName: 'boDisabled', attribute: 'disabled' },
{ directiveName: 'boValue', attribute: 'value' },
{ directiveName: 'boAttr', attribute: 'attr' },
{ directiveName: 'boIf', transclude: 'element', terminal: true, priority: 1000 },
{ directiveName: 'boSwitch', require: 'boSwitch', controller: function () { this.cases = {}; } },
{ directiveName: 'boSwitchWhen', transclude: 'element', priority: 800, require: '^boSwitch' },
{ directiveName: 'boSwitchDefault', transclude: 'element', priority: 800, require: '^boSwitch' }
],
function (boDirective)
{
var childPriority = 200;
return bindonceModule.directive(boDirective.directiveName, function ()
{
var bindonceDirective =
{
priority: boDirective.priority || childPriority,
transclude: boDirective.transclude || false,
terminal: boDirective.terminal || false,
require: ['^bindonce'].concat(boDirective.require || []),
controller: boDirective.controller,
compile: function (tElement, tAttrs, transclude)
{
return function (scope, elm, attrs, controllers)
{
var bindonceController = controllers[0];
var name = attrs.boParent;
if (name && bindonceController.group !== name)
{
var element = bindonceController.element.parent();
bindonceController = undefined;
var parentValue;
while (element[0].nodeType !== 9 && element.length)
{
if ((parentValue = element.data('$bindonceController'))
&& parentValue.group === name)
{
bindonceController = parentValue;
break;
}
element = element.parent();
}
if (!bindonceController)
{
throw new Error("No bindonce controller: " + name);
}
}
bindonceController.addBinder(
{
element: elm,
attr: boDirective.attribute || boDirective.directiveName,
attrs: attrs,
value: attrs[boDirective.directiveName],
interpolate: boDirective.interpolate,
group: name,
transclude: transclude,
controller: controllers.slice(1),
scope: scope
});
};
}
};
return bindonceDirective;
});
})
})();

View File

@ -1 +0,0 @@
!function(){"use strict";var e=angular.module("pasvaz.bindonce",[]);e.directive("bindonce",function(){var e=function(e){if(e&&0!==e.length){var t=angular.lowercase(""+e);e=!("f"===t||"0"===t||"false"===t||"no"===t||"n"===t||"[]"===t)}else e=!1;return e},t=parseInt((/msie (\d+)/.exec(angular.lowercase(navigator.userAgent))||[])[1],10);isNaN(t)&&(t=parseInt((/trident\/.*; rv:(\d+)/.exec(angular.lowercase(navigator.userAgent))||[])[1],10));var r={restrict:"AM",controller:["$scope","$element","$attrs","$interpolate",function(r,a,i,n){var c=function(t,r,a){var i="show"===r?"":"none",n="hide"===r?"":"none";t.css("display",e(a)?i:n)},o=function(e,t){if(angular.isObject(t)&&!angular.isArray(t)){var r=[];angular.forEach(t,function(e,t){e&&r.push(t)}),t=r}t&&e.addClass(angular.isArray(t)?t.join(" "):t)},s=function(e,t){e.transclude(t,function(t){var r=e.element.parent(),a=e.element&&e.element[e.element.length-1],i=r&&r[0]||a&&a.parentNode,n=a&&a.nextSibling||null;angular.forEach(t,function(e){i.insertBefore(e,n)})})},l={watcherRemover:void 0,binders:[],group:i.boName,element:a,ran:!1,addBinder:function(e){this.binders.push(e),this.ran&&this.runBinders()},setupWatcher:function(e){var t=this;this.watcherRemover=r.$watch(e,function(e){void 0!==e&&(t.removeWatcher(),t.checkBindonce(e))},!0)},checkBindonce:function(e){var t=this,r=e.$promise?e.$promise.then:e.then;"function"==typeof r?r(function(){t.runBinders()}):t.runBinders()},removeWatcher:function(){void 0!==this.watcherRemover&&(this.watcherRemover(),this.watcherRemover=void 0)},runBinders:function(){for(;this.binders.length>0;){var r=this.binders.shift();if(!this.group||this.group==r.group){var a=r.scope.$eval(r.interpolate?n(r.value):r.value);switch(r.attr){case"boIf":e(a)&&s(r,r.scope.$new());break;case"boSwitch":var i,l=r.controller[0];(i=l.cases["!"+a]||l.cases["?"])&&(r.scope.$eval(r.attrs.change),angular.forEach(i,function(e){s(e,r.scope.$new())}));break;case"boSwitchWhen":var u=r.controller[0];u.cases["!"+r.attrs.boSwitchWhen]=u.cases["!"+r.attrs.boSwitchWhen]||[],u.cases["!"+r.attrs.boSwitchWhen].push({transclude:r.transclude,element:r.element});break;case"boSwitchDefault":var u=r.controller[0];u.cases["?"]=u.cases["?"]||[],u.cases["?"].push({transclude:r.transclude,element:r.element});break;case"hide":case"show":c(r.element,r.attr,a);break;case"class":o(r.element,a);break;case"text":r.element.text(a);break;case"html":r.element.html(a);break;case"style":r.element.css(a);break;case"disabled":r.element.prop("disabled",a);break;case"src":r.element.attr(r.attr,a),t&&r.element.prop("src",a);break;case"attr":angular.forEach(r.attrs,function(e,t){var a,i;t.match(/^boAttr./)&&r.attrs[t]&&(a=t.replace(/^boAttr/,"").replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase(),i=r.scope.$eval(r.attrs[t]),r.element.attr(a,i))});break;case"href":case"alt":case"title":case"id":case"value":r.element.attr(r.attr,a)}}}this.ran=!0}};angular.extend(this,l)}],link:function(e,t,r,a){var i=r.bindonce&&e.$eval(r.bindonce);void 0!==i?a.checkBindonce(i):(a.setupWatcher(r.bindonce),t.bind("$destroy",a.removeWatcher))}};return r}),angular.forEach([{directiveName:"boShow",attribute:"show"},{directiveName:"boHide",attribute:"hide"},{directiveName:"boClass",attribute:"class"},{directiveName:"boText",attribute:"text"},{directiveName:"boBind",attribute:"text"},{directiveName:"boHtml",attribute:"html"},{directiveName:"boSrcI",attribute:"src",interpolate:!0},{directiveName:"boSrc",attribute:"src"},{directiveName:"boHrefI",attribute:"href",interpolate:!0},{directiveName:"boHref",attribute:"href"},{directiveName:"boAlt",attribute:"alt"},{directiveName:"boTitle",attribute:"title"},{directiveName:"boId",attribute:"id"},{directiveName:"boStyle",attribute:"style"},{directiveName:"boDisabled",attribute:"disabled"},{directiveName:"boValue",attribute:"value"},{directiveName:"boAttr",attribute:"attr"},{directiveName:"boIf",transclude:"element",terminal:!0,priority:1e3},{directiveName:"boSwitch",require:"boSwitch",controller:function(){this.cases={}}},{directiveName:"boSwitchWhen",transclude:"element",priority:800,require:"^boSwitch"},{directiveName:"boSwitchDefault",transclude:"element",priority:800,require:"^boSwitch"}],function(t){var r=200;return e.directive(t.directiveName,function(){var e={priority:t.priority||r,transclude:t.transclude||!1,terminal:t.terminal||!1,require:["^bindonce"].concat(t.require||[]),controller:t.controller,compile:function(e,r,a){return function(e,r,i,n){var c=n[0],o=i.boParent;if(o&&c.group!==o){var s=c.element.parent();c=void 0;for(var l;9!==s[0].nodeType&&s.length;){if((l=s.data("$bindonceController"))&&l.group===o){c=l;break}s=s.parent()}if(!c)throw new Error("No bindonce controller: "+o)}c.addBinder({element:r,attr:t.attribute||t.directiveName,attrs:i,value:i[t.directiveName],interpolate:t.interpolate,group:o,transclude:a,controller:n.slice(1),scope:e})}}};return e})})}();

View File

@ -1,28 +0,0 @@
{
"name": "angular-bindonce",
"version": "0.3.3",
"main": "bindonce.js",
"description": "Zero watchers binding directives for AngularJS",
"homepage": "https://github.com/Pasvaz/bindonce",
"author": "Pasquale Vazzana <pasqualevazzana@gmail.com>",
"repository": {
"type": "git",
"url": "https://github.com/Pasvaz/bindonce.git"
},
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"components"
],
"dependencies": {
},
"keywords": [
"angularjs",
"angular",
"directive",
"binding",
"watcher",
"bindonce"
]
}

View File

@ -1,28 +0,0 @@
{
"name": "angular-bindonce",
"version": "0.3.3",
"main": "bindonce.js",
"description": "Zero watchers binding directives for AngularJS",
"homepage": "https://github.com/Pasvaz/bindonce",
"author": "Pasquale Vazzana <pasqualevazzana@gmail.com>",
"repository": {
"type": "git",
"url": "https://github.com/Pasvaz/bindonce.git"
},
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"components"
],
"dependencies": {
},
"keywords": [
"angularjs",
"angular",
"directive",
"binding",
"watcher",
"bindonce"
]
}

View File

@ -1,20 +0,0 @@
{
"name": "angular-mocks",
"version": "1.6.1",
"license": "MIT",
"main": "./angular-mocks.js",
"ignore": [],
"dependencies": {
"angular": "1.6.1"
},
"homepage": "https://github.com/angular/bower-angular-mocks",
"_release": "1.6.1",
"_resolution": {
"type": "version",
"tag": "v1.6.1",
"commit": "d8ac5a2016c9714b7c87284d21a34648036e8eea"
},
"_source": "https://github.com/angular/bower-angular-mocks.git",
"_target": "1.6.1",
"_originalSource": "angular-mocks"
}

View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2016 Angular
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,63 +0,0 @@
# packaged angular-mocks
This repo is for distribution on `npm` and `bower`. The source for this module is in the
[main AngularJS repo](https://github.com/angular/angular.js/tree/master/src/ngMock).
Please file issues and pull requests against that repo.
## Install
You can install this package either with `npm` or with `bower`.
### npm
```shell
npm install angular-mocks
```
You can `require` ngMock modules:
```js
var angular = require('angular');
angular.module('myMod', [
require('angular-animate'),
require('angular-mocks/ngMock'),
require('angular-mocks/ngAnimateMock')
]);
```
### bower
```shell
bower install angular-mocks
```
The mocks are then available at `bower_components/angular-mocks/angular-mocks.js`.
## Documentation
Documentation is available on the
[AngularJS docs site](https://docs.angularjs.org/guide/unit-testing).
## License
The MIT License
Copyright (c) 2010-2015 Google, Inc. http://angularjs.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +0,0 @@
{
"name": "angular-mocks",
"version": "1.6.1",
"license": "MIT",
"main": "./angular-mocks.js",
"ignore": [],
"dependencies": {
"angular": "1.6.1"
}
}

View File

@ -1,2 +0,0 @@
require('./angular-mocks');
module.exports = 'ngAnimateMock';

View File

@ -1,2 +0,0 @@
require('./angular-mocks');
module.exports = 'ngMock';

View File

@ -1,2 +0,0 @@
require('./angular-mocks');
module.exports = 'ngMockE2E';

View File

@ -1,34 +0,0 @@
{
"name": "angular-mocks",
"version": "1.6.1",
"description": "AngularJS mocks for testing",
"main": "angular-mocks.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://github.com/angular/angular.js.git"
},
"keywords": [
"angular",
"framework",
"browser",
"mocks",
"testing",
"client-side"
],
"author": "Angular Core Team <angular-core+npm@google.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/angular/angular.js/issues"
},
"homepage": "http://angularjs.org",
"jspm": {
"shim": {
"angular-mocks": {
"deps": ["angular"]
}
}
}
}

View File

@ -1,36 +0,0 @@
{
"name": "angular-native-dragdrop",
"version": "1.2.2",
"homepage": "http://angular-dragdrop.github.io/angular-dragdrop",
"authors": [
"ganarajpr"
],
"description": "Angular HTML5 Drag and Drop directive written in pure with no dependency on JQuery.",
"main": "draganddrop.js",
"keywords": [
"angular",
"drag",
"drop",
"html5"
],
"dependencies": {
"angular": "^1.3"
},
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"_release": "1.2.2",
"_resolution": {
"type": "version",
"tag": "1.2.2",
"commit": "e630636fdc39fef815c1c534340aa16caf76a04c"
},
"_source": "https://github.com/angular-dragdrop/angular-dragdrop.git",
"_target": "1.2.2",
"_originalSource": "angular-native-dragdrop"
}

View File

@ -1,25 +0,0 @@
/* jshint -W097 */
'use strict';
/* global require */
var jshint = require('gulp-jshint');
var stylish = require('jshint-stylish');
var uglify = require('gulp-uglify');
var rename = require('gulp-rename');
var gulp = require('gulp');
gulp.task('lint', function() {
return gulp.src('./draganddrop.js')
.pipe(jshint())
.pipe(jshint.reporter(stylish));
});
gulp.task('compress', function() {
return gulp.src('./draganddrop.js')
.pipe(uglify())
.pipe(rename({
extname: '.min.js'
}))
.pipe(gulp.dest('./'));
});
gulp.task('default', ['lint', 'compress']);

View File

@ -1,10 +0,0 @@
The MIT License
Copyright (c) 2015 Ganaraj P R, [Nebithi](http://www.nebithi.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,36 +0,0 @@
#Angular-DragDrop
[![npm version](http://img.shields.io/npm/v/angular-native-dragdrop.svg?style=flat)](https://npmjs.org/package/angular-native-dragdrop)
[![Build status](http://img.shields.io/travis/angular-dragdrop/angular-dragdrop.svg?style=flat)](https://travis-ci.org/angular-dragdrop/angular-dragdrop)
[![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/ganarajpr/angular-dragdrop?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
Angular-DragDrop is a angular HTML5 Drag and Drop directive written in pure with no dependency on JQuery.
This is based on the work done by Jason Turim. While this [blog post](http://jasonturim.wordpress.com/2013/09/01/angularjs-drag-and-drop/) was the inspiration for creating a native Drag and Drop solution, the intention was to create something that was more generic.
This implementation is mainly different from the one posted in the blog in the following areas :
1. Angular-DragDrop does not create an isolate scope. This has huge benefits when it comes to working with other directives. **NOTE :** It also does not pollute the scope with any variables or functions.
2. It does not depend on any kind of an ID attribute ( being either present or generated on the fly ).
3. It allows one to create channels on which different drag and drop directive combinations can work on in the same page ( more on this later ) .
Pull requests are welcome.
[Documentation](http://angular-dragdrop.github.io/angular-dragdrop/)
#Looking for Active Contributors.
This repo needs active contributers and maintainers. If you are interested in being one of the people who would like to actively maintain this repo, please let me know.
The MIT License
Copyright (c) 2014 Ganaraj P R, [Nebithi](http://www.nebithi.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,27 +0,0 @@
{
"name": "angular-native-dragdrop",
"version": "1.2.1",
"homepage": "http://angular-dragdrop.github.io/angular-dragdrop",
"authors": [
"ganarajpr"
],
"description": "Angular HTML5 Drag and Drop directive written in pure with no dependency on JQuery.",
"main": "draganddrop.js",
"keywords": [
"angular",
"drag",
"drop",
"html5"
],
"dependencies": {
"angular": "^1.3"
},
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
]
}

View File

@ -1,29 +0,0 @@
body {
background-color: #f8f7f8;
}
.heading {
border-bottom: 1px solid #b7b7b7;
padding-bottom: 10px;
margin-bottom: 10px;
}
.topRow {
margin-bottom: 30px;
}
.on-drag-enter {
background-color : #677ba6;
}
.on-drag-enter-custom {
background-color : #d78cc7;
}
.on-drag-hover {
background-color : #3eb352;
}
.on-drag-hover-custom {
background-color : #d7a931;
}

View File

@ -1,178 +0,0 @@
<!DOCTYPE html>
<html ng-app="app">
<head>
<meta charset="utf-8" />
<title>Angular DragDrop (Demo)</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="css/styles.css" />
<script data-require="angular.js@1.4.x" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular.js" data-semver="1.4.3"></script>
<script src="http://pc035860.github.io/angular-highlightjs/angular-highlightjs.min.js"></script>
<script src="js/app.js"></script>
<script src="../draganddrop.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
</head>
<body >
<div class="container">
<div class="row topRow">
<h4 class="heading">
Drag and drop between the two lists.
</h4>
</div>
<h3>Beasts</h3>
<p>Left column of beasts is not draggable and accepts both beasts and priests</p>
<hr>
<div class="row" ng-controller="MainCtrl">
<div class="col-xs-6">
<ul ui-on-drop="onDrop($event,$data,men)" drag-enter-class="on-drag-enter-custom"
drop-channel="beasts,priests"
drop-validate="dropValidateHandler($drop, $event, $data)">
<li ui-draggable="false" drag="man" drag-channel="beasts"
on-drop-success="dropSuccessHandler($event,$index,men)"
ng-repeat="man in men track by $index">
{{man}}
</li>
</ul>
</div>
<div class="col-xs-6">
<ul ui-on-drop="onDrop($event,$data,women)"
drop-channel="beasts"
drop-validate="dropValidateHandler($drop, $event, $data)">
<li ui-draggable="true" drag="woman" drag-channel="beasts"
on-drop-success="dropSuccessHandler($event,$index,women)"
on-drop-failure="dropFailureHandler($event,$index,women)"
ng-repeat="woman in women track by $index">
{{woman}}
</li>
</ul>
</div>
</div>
<hr>
<h3>Priests</h3>
<hr>
<div class="row" ng-controller="MainCtrl">
<div class="col-xs-6">
<ul ui-on-drop="onDrop($event,$data,men)"
drop-channel="priests"
drop-validate="dropValidateHandler($drop, $event, $data)">
<li ui-draggable="true" drag="man"
drag-channel="priests"
on-drop-success="dropSuccessHandler($event,$index,men)"
ng-repeat="man in men track by $index">
{{man}}
</li>
</ul>
</div>
<div class="col-xs-6">
<ul ui-on-drop="onDrop($event,$data,women)"
drop-channel="priests"
drop-validate="dropValidateHandler($drop, $event, $data)">
<li ui-draggable="true" drag="woman"
drag-channel="priests"
on-drop-success="dropSuccessHandler($event,$index,women)"
ng-repeat="woman in women track by $index">
{{woman}}
</li>
</ul>
</div>
</div>
<hr>
<h3>Terrorists</h3>
<p>Each terrorist list item accepts a new terrorist. Shows inserting into a particular
position in an array.</p>
<hr>
<div class="row" ng-controller="MainCtrl">
<div class="col-xs-6">
<ul>
<li ui-draggable="true" drag="man"
drag-channel="terrorists2"
drop-validate="dropValidateHandler($drop, $event, $data)"
drag-hover-class="on-drag-hover-custom"
on-drop-success="dropSuccessHandler($event,$index,men)"
ui-on-drop="onDrop($event,$data,men,$index)" drop-channel="terrorists1"
ng-repeat="man in men track by $index">
{{man}}
</li>
</ul>
</div>
<div class="col-xs-6">
<ul>
<li ui-draggable="true" drag="woman"
drag-channel="terrorists1"
drop-validate="dropValidateHandler($drop, $event, $data)"
drag-hover-class="on-drag-hover-custom"
ui-on-drop="onDrop($event,$data,women,$index)" drop-channel="terrorists2"
on-drop-success="dropSuccessHandler($event,$index,women)"
ng-repeat="woman in women track by $index">
{{woman}}
</li>
</ul>
</div>
</div>
<hr>
<h3>Custom Drag Image</h3>
<p>You may specify a drag-image-element-id to use as the drag image. You can use the
<a href="http://www.kryogenix.org/code/browser/custom-drag-image.html" target="_blank">ghost image</a>
technique for the custom drag image.</p>
<hr>
<div class="row" ng-controller="MainCtrl">
<div class="col-xs-6">
<ul>
<li ui-draggable="true" drag="man"
drag-channel="customImage2"
drop-validate="dropValidateHandler($drop, $event, $data)"
drag-hover-class="on-drag-hover-custom"
drag-image-element-id="getCustomDragElementId($index)"
on-drop-success="dropSuccessHandler($event,$index,men)"
ui-on-drop="onDrop($event,$data,men,$index)"
drop-channel="customImage1"
ng-repeat="man in men track by $index">
{{man}}
</li>
</ul>
</div>
<div class="col-xs-6">
<ul>
<li ui-draggable="true" drag="woman"
drag-channel="customImage1"
drop-validate="dropValidateHandler($drop, $event, $data)"
drag-hover-class="on-drag-hover-custom"
drag-image-element-id="getCustomDragElementId($index)"
ui-on-drop="onDrop($event,$data,women,$index)"
drop-channel="customImage2"
on-drop-success="dropSuccessHandler($event,$index,women)"
ng-repeat="woman in women track by $index">
{{woman}}
</li>
</ul>
</div>
</div>
<div id="customDrag0" class="alert alert-info" style="max-width: 200px">Custom drag image #1</div>
<div id="customDrag1" class="alert alert-danger" style="position: absolute; top: 0; right: 0; z-index: -2; max-width: 200px">Custom drag image #2</div>
<div id="coverUp" style="position: absolute; top: 0; right: 0; z-index: -1; background-color: white; width: 200px; height: 60px"></div>
</div>
</body>
</html>

View File

@ -1,54 +0,0 @@
angular.module('app', [
'hljs',
'ang-drag-drop'
]).controller('MainCtrl', function($scope) {
$scope.men = [
'John',
'Jack',
'Mark',
'Ernie',
'Mike (Locked)'
];
$scope.women = [
'Jane',
'Jill',
'Betty',
'Mary'
];
$scope.addText = '';
$scope.dropValidateHandler = function($drop, $event, $data) {
if ($data === 'Mike (Locked)') {
return false;
}
if ($drop.element[0] === $event.srcElement.parentNode) {
// Don't allow moving to same container
return false;
}
return true;
};
$scope.dropSuccessHandler = function($event, index, array) {
array.splice(index, 1);
};
$scope.dropFailureHandler = function($event, index, array) {
alert(array[index] + ' could be dropped into left list!')
};
$scope.onDrop = function($event, $data, array, index) {
if (index !== undefined) {
array.splice(index, 0, $data);
} else {
array.push($data);
}
};
$scope.getCustomDragElementId = function (index) {
return 'customDrag' + (index % 2);
}
});

View File

@ -1,8 +0,0 @@
#Examples
##Simple Usage
<iframe style="width: 100%; height: 500px" src="http://embed.plnkr.co/5RLvCpDPoRcEk6u77dBM" frameborder="0" allowfullscreen="allowfullscreen"></iframe>
##With drop validation
<iframe style="width: 100%; height: 500px" src="http://embed.plnkr.co/xRmz4TlCvlJKxybGrhQH" frameborder="0" allowfullscreen="allowfullscreen"></iframe>

View File

@ -1,26 +0,0 @@
#Installation
##Download
Download the file [**draganddrop.min.js**](https://raw.githubusercontent.com/angular-dragdrop/angular-dragdrop/master/draganddrop.min.js) or [**draganddrop.js**](https://raw.githubusercontent.com/angular-dragdrop/angular-dragdrop/master/draganddrop.js).
##Bower
You can also install via Bower using
`bower install angular-native-dragdrop --save`
---
#Usage
##Step - 1 **Add script**
```
<script src="path/to/draganddrop.min.js"></script>
```
##Step - 2 **Include in app**
```
myApp = angular.module('myApp','ang-drag-drop');
```
##Step - 3 ***Profit!!***

View File

@ -1,120 +0,0 @@
# Angular Drag and Drop
**A Native ( without jquery ) Drag and Drop directive for AngularJS using HTML5 Drag and Drop**
---
##ui-draggable(expression)
Directive in module ang-drag-drop (since 1.0.5 - old module name ngDragDrop)
The ui-draggable attribute tells Angular that the element is draggable. ui-draggable takes an expression as the attribute value. The expression should evaluate to either true or false. You can toggle the draggability of an element using this expression.
###Additional Attributes
####_**drag**_(variable)
The class used to mark child elements of draggable object to be used as drag handle. Default class name is `drag-handle`
**NOTE**: If attribute is not present drag handle feature is not active.
####_**drag-handle-class**_(string)
The `drag` property is used to assign the data that needs to be passed along with the dragging element.
####_**on-drop-success**_(function)
The `on-drop-success` attribute takes a function. We can consider this to be an on-drop-success handler function. This can be useful if you need to do some post processing after the dragged element is dropped successfully on the drop site.
**NOTE**: This callback function is only called when the drop succeeds.
You can request the `drag-end` event ( very similiar to requesting the click event in `ng-click` ) by passing `$event` in the event handler.
####_**on-drop-failure**_(function)
The `on-drop-failure` attribute takes a function. We can consider this to be an on-drop-failure handler function. This can be useful if you need to do some post processing after the dragged element is dropped unsuccessfully on any drop site.
**NOTE**: This callback function is only called when the drop fails.
You can request the `drag-end` event ( very similiar to requesting the click event in `ng-click` ) by passing `$event` in the event handler.
####_**drag-channel**_(string)
The `on-drop-failure` attribute takes a function. We can consider this to be an on-drop-failure handler function. This can be useful if you need to do some post processing after the dragged element is dropped unsuccessfully on any drop site.
**NOTE**: This callback function is only called when the drop fails.
You can request the `drag-end` event ( very similiar to requesting the click event in `ng-click` ) by passing `$event` in the event handler.
###Usage
###Events
On start of dragging an Angular Event `ANGULAR_DRAG_START` is dispatched from the `$rootScope`. The event also carries carries the information about the channel in which the dragging has started.
On end of dragging an Angular Event `ANGULAR_DRAG_END` is dispatched from the `$rootScope`. The event also carries carries the information about the channel in which the dragging has started.
When hovering a draggable element on top of a drop area an Angular Event `ANGULAR_HOVER` is dispatched from the `$rootScope`. The event also carries the information about the channel in which the dragging has started.
---
##ui-on-drop(expression)
Directive in module ang-drag-drop (since 1.0.5 - old module name ngDragDrop)
The `ui-on-drop` attribute tells Angular that the element is a drop site. `ui-on-drop` takes a function as the attribute value. The function will be called when a valid dragged element is dropped in that location. A valid dragged element is one which has the same channel as the drop location.
**NOTE** : This callback function is only called when the drop succeeds.
The `ui-on-drop` callback can request additional parameters. The data that is dragged is available to the callback as $data and its channel as `$channel`. Apart from this the drop event is exposed as `$event`.
###Additional Attributes
####_**drop-channel**_(variable)
The channel that the drop site accepts. The dragged element should have the same channel as this drop site for it to be droppable at this location. It is possible to provide comma separated list of channels.
**NOTE**: Also special value of `drag-channel` attribute is available to accept dragged element with any channel value — *
####_**drop-validate**_(function)
Extra validation that makes sure that the drop site accepts the dragged element beyond having the same channel. If not defined, no extra validation is made.
**NOTE**: This callback function is called only if the channel condition is met, when the element starts being dragged
####_**drag-enter-class**_(string)
The class that will be added to the the droppable element when a dragged element ( which is droppable ) enters the drop location. The default value for this is `on-drag-enter`
####_**drag-hover-class**_(string)
The class that will be added to the drop area element when hovering with an element. The default value for this is `on-drag-hover`
###Usage
###Events
On start of dragging an Angular Event `ANGULAR_DRAG_START` is dispatched from the `$rootScope`. The event also carries carries the information about the channel in which the dragging has started.
On end of dragging an Angular Event `ANGULAR_DRAG_END` is dispatched from the `$rootScope`. The event also carries carries the information about the channel in which the dragging has started.
When hovering a draggable element on top of a drop area an Angular Event `ANGULAR_HOVER` is dispatched from the `$rootScope`. The event also carries the information about the channel in which the dragging has started.

Some files were not shown because too many files have changed in this diff Show More