Compare commits

..

1541 Commits

Author SHA1 Message Date
Torkel Ödegaard e0258617ee Create pid file before runtime init, Fixes #1990 2015-05-14 10:35:02 +02:00
Torkel Ödegaard d10ce90936 Fixed XSS issue with file based dashboards, was really casued by an issue with alertSrv accepting html in message alerts 2015-04-29 15:50:47 +02:00
Torkel Ödegaard 5175cf70ef fixed version in package.json 2015-04-27 15:07:13 +02:00
Torkel Ödegaard 5cb7721ab2 Updated config sample.ini with oauth allow_sign_up 2015-04-27 15:06:42 +02:00
Torkel Ödegaard d66e510c42 Merge pull request #1867 from maddenca/patch-1
Documented units for timepicker
2015-04-26 15:35:59 +02:00
Torkel Ödegaard e410fbb558 Corrected SQL migration for snapshot table column type change, #1880 2015-04-25 20:28:50 +02:00
Torkel Ödegaard 4eeaa6bf9d Merge pull request #1881 from raintank/issue1880
fixes #1880 correct mysql statement for modifying column data type
2015-04-25 20:24:22 +02:00
Anthony Woods f227002a80 fixes #1880 dashboard_snapshot table does not have a data column 2015-04-25 18:51:25 +08:00
Anthony Woods d84d92f73c fixes #1880 correct mysql statement for modifying column data type 2015-04-25 18:46:30 +08:00
Torkel Ödegaard 380b7f4d94 updated version to 2.1.0-pre1 2015-04-24 15:57:20 +02:00
Torkel Ödegaard bad2d1a974 Merge pull request #1869 from marknoe1/extra_uom
Added units for voltage, current and frequency
2015-04-24 15:55:53 +02:00
Torkel Ödegaard 4fd2c0570c Replaced slug dependency with one that did not use gopkgs.com 2015-04-24 15:46:16 +02:00
Torkel Ödegaard 7064d87106 Firefox/IE issue, invisible text in dashboard search fixed, Fixes #1872 2015-04-24 11:20:09 +02:00
Mark Oellermann f8ceb341d6 Added units for voltage, current and frequency 2015-04-24 13:23:54 +10:00
Chris Madden 3fdab59029 Documented units for timepicker
Timepicker units (like s, m, M, etc) were not in the documentation so I have added them.
2015-04-23 23:53:15 +02:00
Torkel Ödegaard b99ee2562e Restored sql integration tests to use in mem sqlite3 2015-04-23 16:21:49 +02:00
Torkel Ödegaard 8526025792 MySQL: Dashboard.data column type changed to mediumtext (sql migration added), Fixes #1863 2015-04-23 16:18:46 +02:00
Torkel Ödegaard d92fae07a5 Merge pull request #1860 from aibou/master
Fix and update documents
2015-04-23 14:55:17 +02:00
aibou 968b1b4308 Fix and update documents 2015-04-23 17:42:04 +09:00
Torkel Ödegaard e9a174d1c5 Merge pull request #1854 from ton31337/master
Remove exit 0 at the end of init.d script
2015-04-23 08:45:26 +02:00
Torkel Ödegaard 2c52224013 /api/login/ping Fix for issue when behind reverse proxy and subpath, Fixes #1857 2015-04-23 08:24:30 +02:00
Donatas Abraitis 869cf705e0 Remove exit 0 at the end of init.d script 2015-04-22 22:29:56 +03:00
Torkel Ödegaard 9f6a348851 Updated install docs with 2.0.2 version 2015-04-22 19:23:28 +02:00
Torkel Ödegaard aa52f6675c updated changelog 2015-04-22 17:56:07 +02:00
Torkel Ödegaard 9cc5e981dd Bumped version to 2.0.2 2015-04-22 17:51:50 +02:00
Torkel Ödegaard da74fa0862 Updated configuration and http api docs 2015-04-22 17:44:47 +02:00
Torkel Ödegaard 1dfc576263 Panel timeshift: You can now use panel timeshift without a relative time override, Fixes #1848 2015-04-22 13:54:31 +02:00
Torkel Ödegaard 170e8a5d76 Another docs update 2015-04-22 13:47:15 +02:00
Torkel Ödegaard bd0bf3747e Began work on http api docs, #1822 2015-04-22 13:23:33 +02:00
Torkel Ödegaard a11b180480 Snapshots: Fixed issue with snapshoting dashboards with an interval template variable, Fixes #1846 2015-04-22 12:27:12 +02:00
Torkel Ödegaard f8cfbedefd Removed semver dependency from go build script 2015-04-22 08:56:14 +02:00
Torkel Ödegaard 90a77bdf85 Fixed failure loop in init.d script when grafana will not start (due to permission issue or other), Fixes #1837 2015-04-22 08:47:53 +02:00
Torkel Ödegaard e438d21cc0 fixed minor issue in build file 2015-04-22 08:26:49 +02:00
Torkel Ödegaard dc88fff704 Migration: Import dashboards from Elasticsearch was capped at 100, now capped at 10000, Fixes #1843 2015-04-22 07:50:23 +02:00
Torkel Ödegaard 8ff316252e Update info text in grafana test data source, Fixes #1840 2015-04-21 21:17:32 +02:00
Torkel Ödegaard dabdf1b7fd Graph Panel + Legend Table mode: Many series casued zero height graph, now legend will never reduce the height of the graph below 50% of row height, Fixes #1832 2015-04-21 18:46:44 +02:00
Torkel Ödegaard 6fb6e44ece updated rpm install docs 2015-04-21 17:17:08 +02:00
Torkel Ödegaard 6df299886e Updated windows install instructions 2015-04-21 13:30:15 +02:00
Torkel Ödegaard b25411ef6b Updated license link on docs page 2015-04-21 10:23:10 +02:00
Torkel Ödegaard dfa5b005cc Updated docs makefile 2015-04-21 10:20:55 +02:00
Torkel Odegaard 73b512859a Small ui align fix to graph axis and grid edit ui 2015-04-21 09:48:50 +02:00
Torkel Ödegaard c2a4e4720b Merge pull request #1827 from bergquist/set_default_line_fill_to_1
Change default values for linewidth and fill.
2015-04-21 09:43:46 +02:00
carl bergquist 74c5015648 Change default values for linewidth and fill. 2015-04-21 09:27:15 +02:00
Torkel Odegaard 5bddf79d09 Correctly determine arch for windows builds 2015-04-21 08:56:32 +02:00
Torkel Odegaard 05bed2fd8a Fixed folder location for readme and license files in bin tar / zip package 2015-04-21 08:20:34 +02:00
Torkel Ödegaard e549a9c09c Fixed minor docs issue 2015-04-20 18:16:55 +02:00
Torkel Ödegaard 8ef3ee1319 Updated appveyor spec file again (windows CI server, hope to get windows builds soon) 2015-04-20 18:09:57 +02:00
Torkel Ödegaard 929dc2b29c updated appveyor (Windows ci build) spec file 2015-04-20 16:19:55 +02:00
Torkel Ödegaard a5d3c1a655 Updated docs 2015-04-20 14:20:01 +02:00
Torkel Ödegaard a933192b56 Updated docs version selector 2015-04-20 13:46:14 +02:00
Torkel Ödegaard 41565335ed Updated docs with new latest packages 2015-04-20 13:35:39 +02:00
Torkel Ödegaard 9540e12e2e Updated version to 2.0.1 to work around rpm and deb versioning scheme does not support regular semver prerelease naming standard, it seems like 2.0.0-beta3 is newer than 2.0.0 for rpm and deb packages. 2015-04-20 13:21:42 +02:00
Torkel Ödegaard 9766106686 Added link to rpm GPG key 2015-04-20 12:38:49 +02:00
Torkel Ödegaard 3d8beb918b Docs updates and update to tar package 2015-04-20 12:33:32 +02:00
Torkel Ödegaard 123ea331e6 Updated build.go to create latest packages correctly 2015-04-20 12:10:54 +02:00
Torkel Ödegaard b5a77d4f4a Updated version to 2.0.0 and updated changelog 2015-04-20 11:54:36 +02:00
Torkel Ödegaard 9852972db8 Merge remote-tracking branch 'origin/nopzor1200-patch-1' 2015-04-20 11:47:36 +02:00
Torkel Ödegaard b286ed6bb8 Small UI polish to playlist creation view 2015-04-20 11:47:09 +02:00
Torkel Odegaard b56a084810 Updated nodejs version in appveyor ci build spec file 2015-04-20 09:19:19 +02:00
Torkel Odegaard c9a28ddd38 Windows build: updates to build stuff to create better windows packages 2015-04-20 08:10:23 +02:00
Torkel Odegaard bb7ee1c5a3 Panel title: switched from context-menu cursor to pointer, context-menu does not work on windows in chrome and firefox, but works on other platforms :( 2015-04-20 07:29:25 +02:00
nopzor1200 1772e15b46 Update index.md 2015-04-19 23:24:35 -04:00
nopzor1200 360efdf71f Update whats-new-in-v2.md 2015-04-19 23:18:33 -04:00
nopzor1200 56d1e6834a Update timerange.md 2015-04-19 22:03:18 -04:00
nopzor1200 f1e267498e Update timerange.md 2015-04-19 22:02:44 -04:00
nopzor1200 4ba6bf8e27 Update playlist.md 2015-04-19 21:46:22 -04:00
nopzor1200 101c44f22d Update playlist.md 2015-04-19 21:37:34 -04:00
nopzor1200 7f967524b9 Update index.md 2015-04-19 21:27:37 -04:00
Torkel Odegaard 01e171373c Updated appvayor file again 2015-04-19 21:31:42 +02:00
Torkel Odegaard 229a4a8aab appveyor, install mingw 2015-04-19 20:59:14 +02:00
Torkel Odegaard f6feb455a9 Updated to appveyor ci spec file 2015-04-19 20:45:18 +02:00
Torkel Odegaard fbf4eac257 Added appveyor, trying to test if a windows CI build will be possible 2015-04-19 20:38:24 +02:00
nopzor1200 48b5d235d8 Update index.md 2015-04-19 14:25:10 -04:00
Torkel Odegaard ebdbbbc000 Updated building on windows instructions in the docs 2015-04-19 09:44:14 +02:00
Torkel Odegaard 1edb13d715 Fixed gofmt formating 2015-04-19 09:29:08 +02:00
Torkel Ödegaard aa8beda4ec Merge pull request #1800 from alienth/allowsignup
Add allow_sign_up setting for auth.google/github.
2015-04-19 09:16:48 +02:00
Torkel Odegaard 7178dcad35 Windows: Fixed runtime issue with file logging config that casued errors on startup 2015-04-19 09:14:50 +02:00
Jason Harvey ddaac50a25 Add allow_sign_up override for auth.google/github. 2015-04-16 13:43:18 -08:00
Torkel Ödegaard a446286869 Fixed issue with unsaved changes srv being triggerd by starring / unstarring a dashboard, Fixes #1795 2015-04-16 17:21:03 +02:00
Torkel Ödegaard c54f0fe58b Merge pull request #1783 from williamjoy/master
oauth google: read api_url from config file
2015-04-16 08:15:31 +02:00
Torkel Ödegaard 7c8166cad2 updated build script 2015-04-15 15:41:56 +02:00
Torkel Ödegaard 3db2aaada6 Bumped version to rc1 2015-04-15 15:17:19 +02:00
Torkel Ödegaard 649d40ed66 Data source proxy: Fixed issue with using data source proxy when grafana is behind nginx suburl, Fixes #1784 2015-04-15 15:13:52 +02:00
Torkel Ödegaard d71b626114 Graph Panel: Legend in table mode now aligns, graph area is reduced depending on how many series, Fixes #1786, Fixes #1749 2015-04-15 14:53:20 +02:00
Torkel Ödegaard fc47fb8f64 Backend auth: remember cookie is needed for oauth logins as well 2015-04-15 13:38:38 +02:00
Torkel Ödegaard 10e5e5c400 fixed godep dependency 2015-04-15 13:31:30 +02:00
Torkel Ödegaard 942e8fdba8 Support for unicode / international characters in dashboard title (improved slugify), Fixes #1734, Fixes #827 2015-04-15 12:36:02 +02:00
Torkel Ödegaard 6ba8854854 Readded link in top header, Fixes #1756 2015-04-15 12:17:31 +02:00
William Wei b7c0d99cdf get google api url from config file
instead of using https://www.googleapis.com/oauth2/v1/userinfo in
code.
2015-04-15 18:11:54 +08:00
Lichun Wei bc4584d9fe Merge pull request #1 from grafana/master
sync from upstream
2015-04-15 18:04:52 +08:00
Torkel Ödegaard 53b1d6a5b2 Row editor: increased width of title field, Fixes #1755 2015-04-15 10:52:09 +02:00
Torkel Ödegaard 836e4b4911 Dashboard snapshot: Should not require login to view snapshot, Fixes #1780 2015-04-15 10:39:03 +02:00
Torkel Ödegaard e9afd30b47 Updated changelog with, Github OAuth: Now works with Github for Enterprise, thanks @williamjoy 2015-04-15 10:31:56 +02:00
Torkel Ödegaard aefbcb7049 Merge pull request #1782 from williamjoy/master
#1781 try fix oauth with github enterprise - add config of api_url
2015-04-15 10:21:09 +02:00
William Wei 4fd2622e55 grafana/grafana#1781 add oauth api url as config
read github api from config file instead of using public github:
http://api.github.com/user

this would be useful when you are using github enterprise edition
2015-04-15 14:18:10 +08:00
William Wei d048e39cde grafana/grafana#1781 add oauth api url as config 2015-04-15 14:03:28 +08:00
Torkel Ödegaard 140f1307b4 Small fix to singlestat panel and unit selector 2015-04-14 17:13:39 +02:00
Torkel Ödegaard 9d10b5ab52 Updated docker docs 2015-04-14 16:53:54 +02:00
Torkel Ödegaard edb39a072e Updated rpm install docs page with yum repo info 2015-04-14 16:49:42 +02:00
Torkel Ödegaard d66d989308 Updated debian install instructions with apt repo 2015-04-14 16:28:57 +02:00
Torkel Ödegaard 05c3b96202 Updated install docs page 2015-04-14 14:06:53 +02:00
Torkel Ödegaard e9c7264fd9 Docs updates 2015-04-13 14:12:03 +02:00
Torkel Ödegaard 0b1c1bb8b4 updated package version 2015-04-13 14:06:33 +02:00
Torkel Ödegaard 47a18bba99 Fixed defaults.ini, missing allowed_domains key, #1729 2015-04-13 14:04:52 +02:00
Torkel Ödegaard 7ffdb488c1 Updated opentsdb docs article with info on adding the data source 2015-04-13 11:49:12 +02:00
Torkel Ödegaard 2c1188f664 Updated influxdb docs page with info on how to add a datasource 2015-04-13 11:35:43 +02:00
Torkel Ödegaard 60ab08ed1b Updated Graphite docs page with instruction on how to add a graphite data source 2015-04-13 09:37:07 +02:00
Torkel Ödegaard 2bb85d216e Fixed fixed decimal option bug, when fixed decimal value was set to zero, Fixes #1764 2015-04-13 09:05:42 +02:00
Torkel Ödegaard 7be8fb8432 Updated install docs 2015-04-13 08:14:13 +02:00
Torkel Ödegaard a4ef1e22d6 Updated docs 2015-04-13 07:55:07 +02:00
Torkel Ödegaard 3335041d39 Trying to split install docs into seperate pages 2015-04-13 07:34:55 +02:00
Torkel Ödegaard 19e881513b updated changelog 2015-04-12 21:34:37 +02:00
Torkel Ödegaard 41777c01f1 Updated config docs 2015-04-12 21:31:35 +02:00
Torkel Ödegaard 6b26c8d46f updated to install docs 2015-04-12 21:28:01 +02:00
Torkel Ödegaard d4339ae9d2 Updated packaging post install 2015-04-12 16:23:43 +02:00
Torkel Ödegaard bdc4d14036 Updated postinstall scripts 2015-04-12 16:19:44 +02:00
Torkel Ödegaard ed82f6c681 Added upgrade step that moves sqlite3 database to new location, #1758 2015-04-12 15:58:08 +02:00
Torkel Ödegaard d4e6ae2804 Fixed issue in rpm init.d script 2015-04-12 15:52:51 +02:00
Torkel Ödegaard a60bf642e1 Updated build.go 2015-04-12 09:51:56 +02:00
Torkel Ödegaard be8a72c35b Updated rpm post install script, #1758 2015-04-12 09:49:28 +02:00
Torkel Ödegaard cc14705404 Update changelog 2015-04-12 09:42:27 +02:00
Torkel Ödegaard 6de584aafc Updated to config file finding, and added homepath command line option 2015-04-12 09:15:49 +02:00
Torkel Ödegaard cc427b1307 Merge branch 'master' into packaging_refactoring 2015-04-10 17:45:01 +02:00
Torkel Ödegaard 00cdb5e36a Merge pull request #1738 from mtanda/suppress_redrawing_by_tooltip
Suppress redrawing when Shared Crosshair is disabled
2015-04-10 17:44:39 +02:00
Torkel Ödegaard 437cd50039 Merge pull request #1740 from craftytrickster/master
When performing a metric find query, the InfluxDb9 datasource needs a qu...
2015-04-10 17:41:56 +02:00
Torkel Ödegaard 61add0ecc6 Merge pull request #1742 from mattttt/patch-1
Typo fix in help_modal.html
2015-04-10 17:41:21 +02:00
Torkel Ödegaard 8accb8865a restart on upgrade default to false 2015-04-10 17:36:25 +02:00
Torkel Ödegaard c44248440e Updated to rpm package 2015-04-10 17:32:40 +02:00
Torkel Ödegaard d32c03ed11 Rpm working again, systemd working on centos 7 2015-04-10 16:20:01 +02:00
Torkel Ödegaard 3e49609099 started work on new rpm package 2015-04-10 11:35:14 +02:00
Torkel Ödegaard c47f4bc97c systemd service is working on debian jessie 2015-04-10 11:26:03 +02:00
Torkel Ödegaard 8ee7d5c2d5 Deb package is starting to work 2015-04-10 11:05:56 +02:00
Torkel Ödegaard 2020fedfdb Fixed issue with overriding default config values via command line 2015-04-10 10:58:32 +02:00
Matt 40b2535728 Typo fix in help_modal.html
Fixed typo in header (Keyboard shutcuts -. Keyboard shortcuts)
2015-04-09 16:18:15 -04:00
David Raifaizen 2d016c5a3f When performing a metric find query, the InfluxDb9 datasource needs a query type in order to return results succesfully. This parameter is not available when called from the templateValuesSrv, which is causing an error 2015-04-09 12:05:18 -04:00
Mitsuhiro Tanda ba3bb57926 suppress all graph redrawing when shared tooltip is enabled 2015-04-10 00:34:23 +09:00
Torkel Ödegaard 4c6d7630cd more work on refining config loading, and packaging 2015-04-09 17:18:04 +02:00
Torkel Ödegaard d1767144a8 Reworking configuration loading and overriding 2015-04-09 12:16:59 +02:00
Torkel Ödegaard a991cda233 Began work on refactoring reading config values 2015-04-08 20:31:42 +02:00
Torkel Ödegaard 9c2040aa9b More deb & rpm work, systemd testing 2015-04-08 16:58:05 +02:00
Torkel Ödegaard 96ee1c17a3 Worked on new deb & rpm packaging and init.d scripts 2015-04-08 14:10:04 +02:00
Torkel Ödegaard 6911572fa1 Panel image rendering: now works under self-signed https cert, Fixes #1726 2015-04-08 09:06:01 +02:00
Torkel Ödegaard 059db533d5 HTTP API: grafana /render calls nows with api keys, Fixes #1649 2015-04-08 08:59:12 +02:00
Torkel Ödegaard f28af4f369 OpenTSDB: another improvement to OpenTSDB query -> query result matching 2015-04-07 21:47:06 +02:00
Torkel Ödegaard 1bd238191c OpenTSDB: better matching for query -> query result series 2015-04-07 21:42:21 +02:00
Torkel Ödegaard c07d48d930 A big refactoring for how sessions are handled, Api calls that authenticate with api key will no longer create a new session 2015-04-07 19:21:14 +02:00
Torkel Ödegaard 00a713c42e Updated configuration docs with cert_key and cert_file info, Closes #1722 2015-04-07 16:48:30 +02:00
Torkel Ödegaard 01c97e69dd Updated influxdb 0.9 datasource config view to mention incomplete state of data source 2015-04-07 13:56:35 +02:00
Torkel Ödegaard 646298f5c5 Handle errors when loading dashboard with template variables that query data sources that cannot be found 2015-04-07 13:48:26 +02:00
Torkel Ödegaard 8f73328e25 Fixed datasource proxy and session timeout issue for OpenTSDB, #1667 2015-04-07 09:53:59 +02:00
Torkel Ödegaard b5112aeee2 Data source proxy, and session timeout fix for influxdb, #1667 2015-04-07 09:50:03 +02:00
Torkel Ödegaard 22adf0d06e Datasource proxy & session timeout fix (casued 401 Unauthorized error after a while), Fixes #1667 2015-04-07 09:25:00 +02:00
Torkel Ödegaard 382f7066d9 Fixed importing dashboards from graphite web issue 2015-04-06 14:46:06 +02:00
Torkel Ödegaard 08c19692ea Updated configuration docs 2015-04-06 14:19:18 +02:00
Torkel Ödegaard eb575685aa OAuth: Specify allowed email address domains for google or and github oauth logins, Closes #1660 2015-04-06 14:16:22 +02:00
Torkel Ödegaard 7a95451288 Fixed graph tooltip lingering when changing dashboard using keyboard, Fixes #1700 2015-04-06 11:42:50 +02:00
Torkel Ödegaard 538ec7c0a0 Unsaved changes: Do not show for snapshots, scripted and file based dashboards, Fixes #1707 2015-04-06 11:22:35 +02:00
Torkel Ödegaard ea800dd838 Added login to organization users table, Fixes #1683 2015-04-04 10:14:51 +02:00
Torkel Ödegaard b8c378f2f0 Updated golang/x/oauth2 dependency, #1710 2015-04-04 09:50:25 +02:00
Torkel Ödegaard e86bedddb2 Updated gitignore file 2015-04-02 15:37:22 +02:00
Torkel Ödegaard 1e1a6df480 Playlist: fixed issue where dashboard title was not visible when playing playlist 2015-04-02 15:37:03 +02:00
Torkel Ödegaard 7aa1ab855e Row editor fix: fixed issue when opening row editor when dashboard settings view is opened, Fixes #1688 2015-04-02 10:05:33 +02:00
Torkel Ödegaard 776f15055a Fixed grafana version in dash settings footer, Fixes #1699 2015-04-02 09:50:57 +02:00
Torkel Ödegaard b291b18a87 Unsaved changes improvements: ignore row collapse state, ignore graph legend sort order, Closes #1702 2015-04-02 09:44:16 +02:00
Torkel Ödegaard e6492f7db9 Share modal: Override UI theme via URL param for Share link, rendered panel, or embedded panel, Closes #1701 2015-04-02 09:21:38 +02:00
Torkel Ödegaard 3b737999d6 Unsaved changes: Do not show for users with role , Fixes #1703 2015-04-02 08:08:22 +02:00
Torkel Ödegaard 00fa7f5e86 Removed unusued config, updated sample config with session config 2015-04-02 07:51:16 +02:00
Torkel Ödegaard fde5ba85a0 Docs: Updated configuration docs with port 80 tips 2015-04-01 17:24:24 +02:00
Torkel Ödegaard a96e4a343c Only create admin user specified in config file when there are no users in the database, Fixes #1680 2015-04-01 16:05:42 +02:00
Torkel Ödegaard 36110d0977 Updated migration docs 2015-04-01 15:56:39 +02:00
Torkel Ödegaard 1f330d7753 Basic auth: Fixed issue when using basic auth proxy infront of Grafana, Fixes #1673 2015-04-01 15:23:26 +02:00
Torkel Ödegaard cf877e6567 added postgres and redis session options 2015-04-01 09:45:42 +02:00
Torkel Ödegaard c3fa68ade8 Data source proxy: Fixed issue with Gzip enabled and data source proxy, Fixes #1675 2015-04-01 09:00:17 +02:00
Torkel Ödegaard 5422d13607 Postgres fix update 2015-04-01 08:26:02 +02:00
Torkel Ödegaard d8f614ed98 Merge pull request #1665 from mattrobenolt/bool
Let xorm convert `False` into the right type
2015-04-01 08:25:20 +02:00
Torkel Ödegaard 4ca125da41 Delete snapshot fix: the action is now shows confirmation directly in the modal (not opening a new tab with api call as in beta1), Fixes #1682 2015-04-01 08:24:03 +02:00
Torkel Ödegaard 3c2bfbfc1c Search: Dashboard results should be sorted alphabetically, Fixes #1685 2015-03-31 22:06:19 +02:00
Torkel Ödegaard 03e336ba9e Fixed minor issue doing snapshot from home (root) dashboard 2015-03-31 21:48:54 +02:00
Torkel Ödegaard 76ea0f432a updated master version to beta2 2015-03-31 19:20:34 +02:00
Torkel Ödegaard 7eb45e1799 MySQL session: fixed problem using mysql as session store, Fixes #1681 2015-03-31 19:18:41 +02:00
Torkel Ödegaard 4af1dcd54f Updated defaults.ini, reverted change in previous commit 2015-03-31 17:42:51 +02:00
Torkel Ödegaard 463c519954 Dashlist panel: added fresh panel when changing limit, Fixes #1677 2015-03-31 17:30:27 +02:00
Torkel Ödegaard 1d0a3660bd Fixed issue with updating default data source, it required page reload to take effect, should not be required, now fixed, Fixes #1671 2015-03-31 14:31:47 +02:00
Torkel Ödegaard b83367063e Small improvement to dashboard loading error handling 2015-03-31 14:03:01 +02:00
Torkel Ödegaard aa724fc11e Remove datasource names in panels from snapshots 2015-03-31 10:48:14 +02:00
Matt Robenolt cedb11ebab Let xorm convert False into the right type 2015-03-30 18:11:39 -07:00
Torkel Ödegaard ffe389af4a Updated to whats new guide 2015-03-30 19:18:15 +02:00
Torkel Ödegaard b313637848 updated confiuration docs 2015-03-30 17:39:16 +02:00
Torkel Ödegaard 2eeae21777 Updated whats new docs guide 2015-03-30 17:35:38 +02:00
Torkel Ödegaard 2af9dfebea Merge pull request #1657 from lfrancke/patch-1
Update index.md
2015-03-30 17:04:28 +02:00
Lars Francke dade6fc191 Update index.md
Fixes two typos and a minor inconsistency
2015-03-30 16:55:37 +02:00
Torkel Ödegaard fb0629a913 Bumbped version to 2.0.0-beta1 2015-03-30 15:45:02 +02:00
Torkel Ödegaard 060d0b777b Updated install docs and changelog 2015-03-30 12:31:12 +02:00
Torkel Ödegaard dc03ca8879 Opening search closes fullscreen edit or fullscreen view 2015-03-30 12:25:17 +02:00
Torkel Ödegaard de51aeab7b updates to whats new doc, and building from source, also small fix for panel snapshot, single panelsnapshot should expand panel span to 12 2015-03-30 11:08:46 +02:00
Torkel Ödegaard b46f8bad6d Added check for allow_user_org_create, updated admin docs 2015-03-30 10:12:24 +02:00
Torkel Ödegaard 8982dc5ed3 Update getting started 2015-03-30 09:38:40 +02:00
Torkel Ödegaard 2d2462fe63 Updated configuration docs 2015-03-30 09:22:58 +02:00
Torkel Ödegaard b1fedca46e Merge pull request #1656 from nopzor1200/patch-5
Update gettingstarted.md
2015-03-30 08:39:38 +02:00
Torkel Ödegaard 929187a934 Merge pull request #1655 from nopzor1200/patch-4
Update admin.md
2015-03-30 08:38:58 +02:00
Torkel Ödegaard 7f34462f9a Merge pull request #1654 from nopzor1200/patch-3
Update influxdb.md
2015-03-30 08:38:14 +02:00
nopzor1200 2c54937104 Update gettingstarted.md
fleshed out 'getting started' text
2015-03-29 16:39:08 -04:00
Torkel Ödegaard 472b8c6e8d Fixed link in docs 2015-03-29 21:27:48 +02:00
Torkel Ödegaard 8e33c2c4d3 Mini work on InfluxDB 0.9 datasource, still needs a lot of work 2015-03-29 20:47:34 +02:00
Torkel Ödegaard c42d09b267 Enabled snapshot sharing of single panels, enabled sharing of snapshot dashboards (but you cannot snapshot a snapshot 2015-03-29 15:01:27 +02:00
Torkel Ödegaard 0f791c9fa8 Refactoring of share modal 2015-03-29 14:30:03 +02:00
Torkel Ödegaard ceb079a7ea removed old docs, need to update export & import docs, and playlist docs 2015-03-29 13:46:37 +02:00
Torkel Ödegaard ce99e7b294 Updated gitignore and added config.js 2015-03-29 13:41:01 +02:00
Torkel Ödegaard a20c276c84 Updated readme 2015-03-29 13:39:51 +02:00
Torkel Ödegaard 2186b09ed7 Removed travis file, circle ci is the primary CI service 2015-03-29 13:34:58 +02:00
Torkel Ödegaard 38db91c7be Fixed failing go unit test 2015-03-29 13:33:34 +02:00
Torkel Ödegaard 902f5d895a Updated readme 2015-03-29 13:30:03 +02:00
Torkel Ödegaard 6d565cb355 Merge branch 'patch-3' of https://github.com/mattrobenolt/grafana 2015-03-29 13:08:28 +02:00
Torkel Ödegaard 6a9256bb00 Bumped version to prebeta3 2015-03-29 13:07:40 +02:00
Torkel Ödegaard b48b2ac656 Updated grunt tasks for the src -> public directory change 2015-03-29 13:06:53 +02:00
Torkel Ödegaard fde11be279 Renamed src directory to public 2015-03-29 12:57:28 +02:00
Torkel Ödegaard adb5d6da84 Merge branch develop (Grafana 2.0 branch) into master 2015-03-29 12:52:07 +02:00
Matt Robenolt 92d869e85c typo: sqllite -> sqlite 2015-03-28 16:26:51 -07:00
nopzor1200 e8e83bdbce Update admin.md 2015-03-28 18:40:25 -04:00
nopzor1200 bb8f8fccca Update influxdb.md
clarified some language, split into influx 0.9 and influx 0.8 since they are now two separate datasources/queryeditors
2015-03-28 17:30:36 -04:00
Torkel Ödegaard 1824caa5ea Updated snapshot share icon size 2015-03-28 21:04:38 +01:00
Torkel Ödegaard dfac87db05 Removed unused function from kbn.js 2015-03-28 18:04:22 +01:00
Torkel Ödegaard 15c8a5e351 Merge pull request #1651 from nopzor1200/patch-1
Updates to migration doc
2015-03-28 18:02:13 +01:00
Torkel Ödegaard 8e9a0eddf5 Custom snapshot topnav header, new custom dashboard snapshot icon, #1623 2015-03-28 17:53:52 +01:00
nopzor1200 1200dd4b75 Update migrating_to2.md 2015-03-28 11:57:00 -04:00
nopzor1200 f969fce4d4 Update migrating_to2.md
minor tweaks
2015-03-28 00:39:13 -04:00
nopzor1200 45419195aa Update migrating_to2.md
some suggested updates
2015-03-28 00:30:39 -04:00
Torkel Ödegaard 80c771c945 Added googla analytics id setting 2015-03-27 17:13:44 +01:00
Torkel Ödegaard 6bd2736116 added transparent panel option 2015-03-27 14:39:03 +01:00
Torkel Ödegaard f7b4f331f3 Updating docs 2015-03-27 14:23:23 +01:00
Torkel Ödegaard c01efad997 Updated config (minor description change) 2015-03-27 11:26:20 +01:00
Torkel Ödegaard 1d64ba3b5d Small style update to submenu (template variables, annotation menu) 2015-03-27 08:39:08 +01:00
Torkel Ödegaard e646ae8be4 updated whats new doc 2015-03-27 07:39:06 +01:00
Torkel Ödegaard d3db49ae3e Fixed snapshot sharing issue 2015-03-27 06:47:58 +01:00
Torkel Ödegaard 7be7aeb70a Fixed sql migration issue with dashboard snapshots 2015-03-26 21:20:44 +01:00
Torkel Ödegaard 541cd2e430 Dashboard snapshot: more work on snapshot deletion, and saving external reference, #1623 2015-03-26 20:59:41 +01:00
Torkel Ödegaard 4322f29f34 Dashboard snapshot: added delete key which can be used to delete snapshots, #1623 2015-03-26 20:34:58 +01:00
Torkel Ödegaard 7d0ae23c0e small docs update 2015-03-26 19:31:43 +01:00
Torkel Ödegaard 0122a9ab18 Updated whats new doc 2015-03-26 18:03:37 +01:00
Torkel Ödegaard 165647184d Updated docs: whats new in 2.0, added sharing reference docs page, #1571 2015-03-26 17:55:07 +01:00
Torkel Ödegaard 2ca86b6085 Changed log base 16 to log base 32, #452 2015-03-26 17:44:36 +01:00
Torkel Ödegaard b04c50537d Progress on InfluxDB 0.9 support, but will take a break, it is impossible to work on this, cannot get any queries that use tags to work with InfluxDB 0.9rc15, just empty response, #1525 2015-03-26 13:51:43 +01:00
Torkel Ödegaard fcac4c057c updated xorm, go-sqlite3 dependencies 2015-03-26 12:41:43 +01:00
Torkel Ödegaard 71aa2ef2c2 Changed reporting interval to 24 hour (1 hour was just for testing) 2015-03-26 12:20:24 +01:00
Torkel Ödegaard 03aa997673 merged with master and fixed ES issue 2015-03-26 12:14:37 +01:00
Torkel Ödegaard df4a00f8ef Merge remote-tracking branch 'origin/master' into develop 2015-03-26 12:09:02 +01:00
Torkel Ödegaard f343a46ca4 Singlestat panel: Fixed error handling, when query fail do not show last value, Fixes #1647 2015-03-26 12:05:08 +01:00
Torkel Ödegaard b1f85dc8f1 Added expire option to dashboard snapshots, #1623 2015-03-26 12:00:52 +01:00
Torkel Ödegaard 722d74a41b Removed unused animate.min.css 2015-03-25 21:27:57 +01:00
Torkel Ödegaard 86a9fe6024 Removed unused bootstrap css 2015-03-25 21:11:21 +01:00
Torkel Ödegaard 68701c5cf9 More refinements to info text on share snapshot dialog, #1623 2015-03-25 20:55:41 +01:00
Torkel Ödegaard f4280ca517 More refinements of dashboard snapshot dialog, #1623 2015-03-25 20:36:48 +01:00
Torkel Ödegaard afeb65b3bf Snapshot sharing fix 2015-03-25 17:18:43 +01:00
Torkel Ödegaard d9e56b678a Fixed intendation issue 2015-03-25 16:35:14 +01:00
Torkel Ödegaard aa60edd9fe Fixed issue with external snapshot publish 2015-03-25 16:30:03 +01:00
Torkel Ödegaard c4b8a9853e updated grunt-jscs dependency 2015-03-25 16:06:13 +01:00
Torkel Ödegaard e640c51f84 Merge pull request #1639 from tuexss/patch-1
readme cleanup
2015-03-25 16:00:06 +01:00
Torkel Ödegaard b37e100058 Merge pull request #1640 from tuexss/tuexss-https
http->https for latest version
2015-03-25 15:59:20 +01:00
Torkel Ödegaard cb3593e472 Lots of small fixes, role viewer hides save icon and some actions in
config dropdown. Snapshot dashboard hides save, star, config menu icons.
Can now embedd panel from snapshotted dashboard.
2015-03-25 15:48:56 +01:00
Torkel Ödegaard 2e6d28027a Removed snapshot from dashboard settings dropdown, its only reached through the share menu 2015-03-25 14:19:14 +01:00
Torkel Ödegaard 9c5e116d09 Fixed small file nameing issue in build script 2015-03-25 14:14:45 +01:00
Torkel Ödegaard 10618637e2 Fixed bug in sql migration, closes #1643 2015-03-25 13:53:58 +01:00
Torkel Ödegaard 4e97df06a3 Removed ghost panel 2015-03-25 13:43:52 +01:00
Torkel Ödegaard abe529b5be Merge pull request #1641 from tuexss/tuexss-https-1
http -> https for external links
2015-03-25 12:32:04 +01:00
Torkel Ödegaard e31a3a64e1 OpenTSDB: Alias patterns (reference tag values), syntax is: or [[tag_tagname]], Closes #1344, match opentsdb response to query, Fixes #1601 2015-03-25 12:27:33 +01:00
tuexss 152b01064a http -> https for external links 2015-03-25 12:17:22 +01:00
tuexss f235b516dc http->https for latest version 2015-03-25 12:14:26 +01:00
tuexss 1f6d5bfd53 readme cleanup 2015-03-25 12:03:20 +01:00
Torkel Ödegaard da833cbc58 Small progress on influxdb 0.9 query editor, #1525 2015-03-25 11:07:12 +01:00
Torkel Ödegaard 9268ecf3e9 Some refinements to dashboard snapshots 2015-03-25 09:04:38 +01:00
Torkel Ödegaard c8687560d6 Merge pull request #1636 from mattrobenolt/patch-2
Fix more Cache-Control headers
2015-03-25 07:22:22 +01:00
Matt Robenolt 5286f0856d Fix more Cache-Control headers
`max-age` is always with an `=`, not a `:`.
2015-03-24 17:30:26 -07:00
Torkel Ödegaard 789363b0ad Added ghost panel that shows up empty rows, this panel will show add panel buttons to more quickly/easier get to add a panel, #1635 2015-03-24 21:10:44 +01:00
Torkel Ödegaard e6918c4b99 Merge branch 'develop' into ghost-panel 2015-03-24 19:53:56 +01:00
Torkel Ödegaard f9cf673f81 removed accidental code, should have been part of ghost-panel branch commit 2015-03-24 19:49:51 +01:00
Torkel Ödegaard cc71b1f07d Ghost panel test 2015-03-24 19:42:39 +01:00
Torkel Ödegaard 7919d79347 Another cache header fix 2015-03-24 17:16:13 +01:00
Torkel Ödegaard 22b78aa037 Merge pull request #1632 from swehner/limit-search-results
Limit ElasticSearch to return only title and tags for dashboard search
2015-03-24 17:04:35 +01:00
Torkel Ödegaard a5c3855233 Added dashboard snapshot metrics 2015-03-24 16:49:12 +01:00
Torkel Ödegaard ddd3df26b1 Fixed docs spelling issue, #1634 2015-03-24 15:52:22 +01:00
Torkel Ödegaard c27db7a347 Small updates to share dashboard snapshot feature 2015-03-24 15:46:17 +01:00
Stefan Wehner 527e802b05 Limit ElasticSearch return to title and tags 2015-03-24 11:37:26 +01:00
Torkel Ödegaard d758729633 Merge pull request #1629 from mattrobenolt/patch-1
Fix format of Cache-Control header
2015-03-24 11:13:55 +01:00
Matt Robenolt 3e9adeefbc Fix format of Cache-Control header 2015-03-23 21:58:29 -07:00
Torkel Ödegaard 5f0e7cd52a Added custom cache control headers for static content 2015-03-23 18:28:59 -04:00
Torkel Ödegaard 98c0209976 Dashboard snapshot: cleanup snapshot data after snapshot, #1623 2015-03-23 17:34:41 -04:00
Torkel Ödegaard 6f2a8e27b8 Dashboard Snapshot: added dashboard snapshot to changelog, #1623 2015-03-23 15:36:18 -04:00
Torkel Ödegaard 41820ccb05 Dashboard Snapshot sharing: singlestat panel now works, #1623 2015-03-23 15:32:38 -04:00
Torkel Ödegaard 4d13a5bffb Fixed failing style check 2015-03-23 14:00:03 -04:00
Torkel Ödegaard 7614ddb318 Updated design for snapshot sharing dialog, #1596 2015-03-23 13:58:30 -04:00
Torkel Ödegaard a5fac17f2b Added public snapshot test, hosted on snapshots.raintank.io 2015-03-23 07:32:03 -04:00
Torkel Ödegaard 49a0ea53c7 Merge branch 'develop' of github.com:grafana/grafana into dashboard_snapshot_poc
Conflicts:
	src/app/features/dashboard/partials/shareDashboard.html
2015-03-22 22:04:13 -04:00
Torkel Ödegaard 44bc2b2d56 Updated conf description, metrics interval 2015-03-22 16:30:28 -04:00
Torkel Ödegaard a26436f59b Server metrics fix 2015-03-22 16:13:16 -04:00
Torkel Ödegaard 1e4c62a70d updated server reporting 2015-03-22 15:45:13 -04:00
Torkel Ödegaard 526f3e1a31 Fixed failing unit test 2015-03-22 15:27:05 -04:00
Torkel Ödegaard c67291da33 Updated 2015-03-22 15:25:21 -04:00
Torkel Ödegaard 9c9ebb4987 Updated server stats 2015-03-22 15:24:35 -04:00
Torkel Ödegaard d987532262 Added server metrics 2015-03-22 15:14:00 -04:00
Torkel Ödegaard 7d4293f849 removed cli commands, need to be mobed to a seperate binary using http api, #1570 2015-03-22 06:48:53 -04:00
Torkel Ödegaard 9bae19a7ec Small fix to logarithmic scale 2015-03-21 15:34:26 -04:00
Torkel Ödegaard bf898138b7 Small update to drilldown link editor 2015-03-21 12:09:04 -04:00
Torkel Ödegaard 245c6dbff0 Share Panel: The share modal now has an embed option, gives you an iframe that you can use to embedd a single graph on another web site, #1622 2015-03-21 12:09:04 -04:00
Torkel Ödegaard f48f5428e5 Adding snapshot storage and route, #1623 2015-03-21 10:56:26 -04:00
Torkel Ödegaard 964f0861d6 more work on dashboard snapshots 2015-03-21 08:53:16 -04:00
Torkel Ödegaard a76758255f Merge branch 'develop' into dashboard_snapshot_poc 2015-03-21 07:29:40 -04:00
Torkel Ödegaard 7db3703275 Share Panel: The share modal now has an embed option, gives you an iframe that you can use to embedd a single graph on another web site, #1622 2015-03-20 22:01:39 -04:00
Torkel Ödegaard 306358e73c Merge pull request #1621 from raintank/1619
fixes #1619 Secure PhantomJS Png rendering
2015-03-20 19:30:58 -04:00
Torkel Ödegaard 2bd2605ae9 Added poc of dashboard snapshot, sharable dashboard with data embedded 2015-03-20 19:16:59 -04:00
Anthony Woods 7010df0fe8 fixes #1619 Secure PhantomJS Png rendering
removes auth hack to allow phantomjs to query pages as a user
without auth.  Instead we pass phantomjs the session cookie,
which it then includes in the request.
2015-03-21 07:14:13 +08:00
Torkel Ödegaard 36a948965b Graph: added log base 16 and log base 1024 scales, #452 2015-03-20 18:12:12 -04:00
Torkel Ödegaard 5d6583ef7b Fixed small issue in share modal introdiced in recent commit 2015-03-20 15:07:38 -04:00
Torkel Ödegaard a8363f02b8 Small changes to log scale handling, #452 2015-03-19 15:16:47 -04:00
Torkel Ödegaard 94c3a07115 Fixed small issue when using 'Save As', dashboard version was not reset 2015-03-19 10:32:47 -04:00
Torkel Ödegaard 30047e6a9f Updated macaron and ini package 2015-03-19 10:17:16 -04:00
Torkel Ödegaard c1d6fcd18d Fixed full screen edit css issue where part of the dashboard became visible 2015-03-18 19:57:54 -04:00
Torkel Ödegaard 94415e2b60 Fix for panel height in fullscreen mode 2015-03-18 19:42:18 -04:00
Torkel Ödegaard d81d0c8c44 Graph: Adds logarithmic scale option (log base 10), Closes #452 2015-03-18 18:51:29 -04:00
Torkel Ödegaard eb8b9c4ac3 Increase api_key.key varchar to 255 length 2015-03-18 13:20:15 -04:00
Torkel Ödegaard 080847ec4d Change: shared tooltip is now enabled by default in graph panel 2015-03-18 12:47:45 -04:00
Torkel Ödegaard bb5eeee82e fixed small issue with share modal 2015-03-17 17:34:00 -04:00
Anthony Woods 422324723a Merge pull request #1609 from raintank/1606
gofmt. remove extra whitespace
2015-03-18 00:55:06 +08:00
Anthony Woods 12e033976b Merge remote-tracking branch 'upstream/develop' into 1606 2015-03-17 23:43:40 +08:00
Anthony Woods fc5839d8f1 gofmt. remove extra whitespace 2015-03-17 23:42:16 +08:00
Anthony Woods 32071445af Merge pull request #1607 from raintank/1606
fixes #1606 emit orgCreated event when new org created
2015-03-17 23:37:24 +08:00
Anthony Woods bce62c49d0 fixes #1606 2015-03-17 23:10:49 +08:00
Anthony Woods 7235bd19ea Merge branch '1606' of github.com:raintank/grafana into 1606 2015-03-17 23:10:33 +08:00
Anthony Woods 6a09a7c398 fixes #1606 emit OrgCreated event when new org created. 2015-03-17 23:09:43 +08:00
Anthony Woods 068ccf1c0f fixes 1606 emit OrgCreated event when new org created. 2015-03-17 23:07:08 +08:00
Torkel Ödegaard 2a1434ce49 Merge pull request #1594 from eheydrick/docs-spelling
correct spelling
2015-03-17 09:46:38 -04:00
Torkel Ödegaard 3fe54894a5 Merge branch 'develop' of github.com:grafana/grafana into develop 2015-03-14 15:29:59 -04:00
Torkel Ödegaard 2fec2c2fa0 Templating: Dashboard will now wait to load until all template variables that have refresh on load set or are initialized via url to be fully loaded and so all variables are in valid state before panels start issuing metric requests. Closes #1255 2015-03-14 15:29:41 -04:00
Torkel Ödegaard ea4dbeaeb9 Merge pull request #1593 from jwilder/jw-docs
Add docs for building the docs locally
2015-03-14 15:36:29 +01:00
Torkel Ödegaard d705ee70f0 Refactoring out http settings to its own partial 2015-03-13 18:42:46 +01:00
Torkel Ödegaard 448717a757 Fixed small issue when changing data source from metrics panel 2015-03-13 17:58:58 +01:00
Torkel Ödegaard 4b3224702c Fixed asset revving issue with css files 2015-03-13 11:11:31 +01:00
Torkel Ödegaard ab307ec7f3 Fixed css issue with title headers 2015-03-13 10:20:39 +01:00
Torkel Ödegaard 3dc9f792e6 Fixed unstar issue 2015-03-13 09:52:59 +01:00
Torkel Ödegaard 9faa1fadcb Updated docs 2015-03-13 09:46:53 +01:00
Torkel Ödegaard cb04b7f4e2 Fixed issue with legend values when all values are negative and some are null, #1468 2015-03-13 09:27:08 +01:00
Torkel Ödegaard de94b48d77 Merge pull request #1597 from jwilder/jw-graph
Update graph docs for v2
2015-03-13 08:32:36 +01:00
Jason Wilder aca83479be Update graph docs for v2
Still needs metric tab for each datasource and time range details.
2015-03-12 17:20:48 -06:00
Eric Heydrick d5564b476f correct spelling 2015-03-12 15:03:59 -07:00
Jason Wilder 3693f36e59 Add docs for building the docs locally 2015-03-12 14:47:49 -06:00
Torkel Ödegaard 17063df3cc Changed default org name form 'main' to 'Main Org.' 2015-03-12 19:43:52 +01:00
Torkel Ödegaard f417d9aa19 Merge pull request #1592 from jwilder/jw-fixes
Anonymous access fixes
2015-03-12 19:41:20 +01:00
Torkel Ödegaard b91b47fc46 Graph & Singlestat: Users can now set decimal precision for legend and tooltips (override auto precision), Closes #1253 2015-03-12 19:36:50 +01:00
Jason Wilder 13206fbb69 Fix default anonymous org name 2015-03-12 11:51:54 -06:00
Jason Wilder d3d896dccd Log more descriptive error when anonymous org is not found
Was logging:

  [middleware.go:78 func·004()] [E] Anonymous access organization error%!(EXTRA <nil>)
2015-03-12 11:49:05 -06:00
Torkel Ödegaard e78b358643 Fixed annotations enabled check from annotationsSrv, Closes #1590 2015-03-12 15:50:39 +01:00
Torkel Ödegaard b757048544 reduced dashboard title font-weight 2015-03-12 15:45:34 +01:00
Torkel Ödegaard ebe41fca53 Moved dashboard title into dashboard search button 2015-03-12 15:10:27 +01:00
Torkel Ödegaard 96ad3a04d4 Removed 'Back to dashboard' top nav button, now have close button on the edit box below panel 2015-03-12 14:03:09 +01:00
Torkel Ödegaard 43ba5842bb Began work on doc for migrating from v1 to v2, new features in v2 overview guide, #1571 2015-03-12 10:52:29 +01:00
Torkel Ödegaard b8979a7253 Merge branch 'docs-1.x' into develop 2015-03-11 20:57:22 +01:00
Torkel Ödegaard 813b851c10 fixed issue in cla docs 2015-03-11 20:56:03 +01:00
Torkel Ödegaard c1d7bef768 added cert_key and cert_file to defaults.ini 2015-03-11 19:44:31 +01:00
Torkel Ödegaard 434a237764 Added ENV variable info to the configuration docs 2015-03-11 19:32:06 +01:00
Torkel Ödegaard 945cb3254c Updated config docs with oauth setup and config instructions 2015-03-11 18:13:48 +01:00
Torkel Ödegaard 9c2edba12b Working on config docs for grafana 2.0 2015-03-11 17:54:06 +01:00
Torkel Ödegaard 477e035f2e Fixed anonymous access mode, Closes #1586 2015-03-11 17:34:11 +01:00
Torkel Ödegaard f3d4d2782f Simplified single org settings, now auto_assign_org, and auto_assign_org_role, new [users] config section, Closes #1585 2015-03-11 16:19:29 +01:00
Torkel Ödegaard 4f03a86414 Began work on configuration docs for Grafana 2.0, #1571 2015-03-11 15:13:52 +01:00
Torkel Ödegaard 6336edbc18 Merge branch 'docs-1.x' into develop 2015-03-11 10:47:09 +01:00
Torkel Ödegaard 1f550be949 updated docs system 2015-03-11 10:46:48 +01:00
Torkel Ödegaard 5f5bb8541b Updated docs 2015-03-11 10:32:12 +01:00
Torkel Ödegaard 70ce5d0a32 Updated install docs 2015-03-10 17:54:07 +01:00
Torkel Ödegaard 5f22e7da1f Restored steps to build setup phase, installing sqlite3, speeds up builds so much in dev 2015-03-10 17:34:28 +01:00
Torkel Ödegaard 2c28f8cdca Began work on grafana 2.0 install instructions 2015-03-10 17:25:11 +01:00
Torkel Ödegaard 4244ed33c8 Merge branch 'docs-1.x' into develop 2015-03-10 16:58:31 +01:00
Torkel Ödegaard 0c62ab0683 updated docs 2015-03-10 16:27:16 +01:00
Torkel Ödegaard aaa98e13b6 updated docs 2015-03-10 16:17:50 +01:00
Torkel Ödegaard 0ff99dddb9 Updated docs 2015-03-10 14:41:19 +01:00
Torkel Ödegaard 2b22d11923 Updated version to prebeta2 2015-03-10 13:55:08 +01:00
Torkel Ödegaard fa2e074b94 Fixed drilldown link issue, Fixes #1579 2015-03-10 13:52:53 +01:00
Torkel Ödegaard e75acce6ed updated package.json upgrade of grunt contrib uglify 2015-03-10 13:46:50 +01:00
Torkel Ödegaard 390589fae2 Merge pull request #1580 from bbinet/fix-uglify-compress-issue
fix uglify TypeError
2015-03-10 13:40:25 +01:00
Bruno Binet ae41fa3011 fix uglify TypeError
This patch fix the following uglify error:
TypeError: Cannot assign to read only property 'warnings' of true
Or we can upgrade grunt-contrib-uglify to latest v0.8.0 version which
should also fix the issue.

This issue has been reported on grunt-contrib-uglify bug tracker:
https://github.com/gruntjs/grunt-contrib-uglify/issues/298
2015-03-10 11:27:17 +01:00
Torkel Ödegaard b693c7515c fixed issue in Dockerfile 2015-03-10 09:08:24 +01:00
Torkel Ödegaard 3119d15de5 Fixed issue in Dockerfile and docs VERSION file 2015-03-10 09:06:27 +01:00
Torkel Ödegaard b99e7ed97e Updated docs version and updated build from source instructions 2015-03-10 09:03:05 +01:00
Torkel Ödegaard f32c34c726 Merge branch 'master' into develop
Conflicts:
	.gitignore
2015-03-10 08:57:14 +01:00
Torkel Ödegaard 15cb1d8980 Added grafana 1.x docs to main repo 2015-03-10 08:56:03 +01:00
Torkel Ödegaard 99259d246b Fixed CLA in new docs 2015-03-10 08:51:26 +01:00
Torkel Ödegaard 8c7076c04a fixed doc image link 2015-03-10 08:20:50 +01:00
Torkel Ödegaard 81bae9a844 Updated docs 2015-03-09 21:58:01 +01:00
Torkel Ödegaard d7ce7271fe Updated docs 2015-03-09 20:20:44 +01:00
Torkel Ödegaard 5ab64987eb Switching to mkdocs for documentation, and docs that are inside the main repo, adding existing docs, #1571 2015-03-09 17:12:59 +01:00
Torkel Ödegaard dc46148ab6 Migrated from wercker to circle CI 2015-03-07 20:30:06 +01:00
Torkel Ödegaard 1b1bbcecec Added go vet step to circle.yml 2015-03-07 19:51:19 +01:00
Torkel Ödegaard ebad9cb502 Fixed gofmt formating, updated precommit hook 2015-03-07 16:37:19 +01:00
Torkel Ödegaard d8005af8ee updated circle with fmt formating test 2015-03-07 16:23:22 +01:00
Torkel Ödegaard ba3ba0aca7 Removed docker as service from circle.yml 2015-03-07 15:58:35 +01:00
Torkel Ödegaard 880893914a Removed deploy step from circle, will go in another repo 2015-03-07 15:24:52 +01:00
Torkel Ödegaard 07a21e921d Updated circle.yml added package step 2015-03-07 14:04:12 +01:00
Torkel Ödegaard 2193f222c5 Fixed bug in build container build script 2015-03-07 13:58:28 +01:00
Torkel Ödegaard f36ea13cb2 Added saving buildcontainer to cache directory 2015-03-07 13:55:12 +01:00
Torkel Ödegaard 595a1fe852 Added back test step to circle.yml 2015-03-07 13:53:10 +01:00
Torkel Ödegaard 3334e6bf61 Fixed bug in circle.yml 2015-03-07 13:48:02 +01:00
Torkel Ödegaard 0a82b75f2a Adding caching of buildcontainer 2015-03-07 13:46:27 +01:00
Torkel Ödegaard 5b563e3841 Updated circle.yml 2015-03-07 13:28:03 +01:00
Torkel Ödegaard 3a98ce775a Updated docker build script 2015-03-07 13:27:02 +01:00
Torkel Ödegaard 8c27a10612 Updated circle.yml 2015-03-07 13:23:57 +01:00
Torkel Ödegaard e3ce8048ad Added deploy step that runs build in centos6 container 2015-03-07 13:22:52 +01:00
Torkel Ödegaard 0d367d4b54 Updated circle.yml 2015-03-07 13:07:22 +01:00
Torkel Ödegaard 028ae7053b Updated circle.yml added npm install and grunt test step 2015-03-07 12:55:26 +01:00
Torkel Ödegaard 3f3b14130b Updated circle 2015-03-07 12:52:31 +01:00
Torkel Ödegaard 820051d40b Updated circle.yml 2015-03-07 12:46:17 +01:00
Torkel Ödegaard 97499aaa75 Updated circle.yml 2015-03-07 12:43:17 +01:00
Torkel Ödegaard cc89bc02f4 Working on trying to get CI builds on centos6 to solve GLIBC issue, #1568 2015-03-07 12:40:19 +01:00
Torkel Ödegaard 4cbcee7e15 Updated init.d script with chkconfig comment lines, now works on centos5 2015-03-06 11:21:41 +01:00
Torkel Ödegaard ab47c2c734 Fixed influxdb 0.9 data source, renamed clone dashboard to Save As... 2015-03-06 10:01:18 +01:00
Torkel Ödegaard 3e6ec19fd4 Fixed bug in CloneDashboardCtrl 2015-03-06 08:23:36 +01:00
Torkel Ödegaard 5719e437ef Merge pull request #1556 from jwilder/jw-export
Add dashboards:export CLI command
2015-03-06 07:59:44 +01:00
Torkel Ödegaard dba7062fbc Some more polish to confirm dialogs 2015-03-05 21:02:25 +01:00
Torkel Ödegaard 6786ffffac Small update to submenu (annotation/templating) css, #1554 2015-03-05 20:45:14 +01:00
Torkel Ödegaard 16320d66f2 Updated build file 2015-03-05 20:11:34 +01:00
Torkel Ödegaard ec6dd35098 Fixed path to ZeroClipboard flash file 2015-03-05 20:10:05 +01:00
Torkel Ödegaard f5f4689e7c Worked on styling for confirm dialogs, #1554 2015-03-05 20:04:54 +01:00
Torkel Ödegaard 259d330822 Began work on modal css polish, #1554 2015-03-05 18:01:41 +01:00
Torkel Ödegaard 79be106711 Fixed javascript style check error 2015-03-05 16:53:08 +01:00
Torkel Ödegaard 5ccd3e2f84 Updated share panel/dash look, added copy to clipboard button (using ZeroClipboard lib), #1554 2015-03-05 16:52:32 +01:00
Torkel Ödegaard 4f8d22796a Merge remote-tracking branch 'origin/master' into develop 2015-03-05 12:26:08 +01:00
Torkel Ödegaard 5be5ee9b1b Fixed panel error handling issue 2015-03-05 12:25:48 +01:00
Torkel Ödegaard 679f2aa262 removed console logging, left by mistake in last commit 2015-03-05 12:19:52 +01:00
Torkel Ödegaard 58034c24ce Merge remote-tracking branch 'origin/master' into develop 2015-03-05 11:17:39 +01:00
Torkel Ödegaard 1ac9b50f21 Increased limit before graph date axis switches to showing date and hour, Fixes #1558 2015-03-05 11:16:48 +01:00
Torkel Ödegaard af9dc4c277 Fixed issue with OpenTSDB editor and checkboxes, Fixes #1559 2015-03-05 11:00:47 +01:00
Torkel Ödegaard f817a8ca3c Merge remote-tracking branch 'origin/master' into develop
Conflicts:
	src/app/directives/grafanaPanel.js
	src/app/features/dashboard/dashboardCtrl.js
2015-03-05 10:45:27 +01:00
Torkel Ödegaard 97e5a04621 Fixed memory leak in bootstrap Typeahead code, Fixes #1497 2015-03-05 10:42:46 +01:00
Torkel Ödegaard 30294740bd Updated build script, changed deb and rpm config path to /etc/grafana, #1476 2015-03-04 16:23:30 +01:00
Torkel Ödegaard 6bf4edade1 Updated build.go to get better version/build names 2015-03-04 14:48:21 +01:00
Torkel Ödegaard de1e762528 Fixed issue using mysql as session store, Closes #1557 2015-03-04 10:34:26 +01:00
Torkel Ödegaard d043ee9407 Fixed js concat issue 2015-03-04 10:04:59 +01:00
Torkel Ödegaard 14766ad38e Updated wercker, moved s3sync to deploy step 2015-03-04 09:12:32 +01:00
Torkel Ödegaard dfcf68f21d Moved npm install before test 2015-03-04 07:20:36 +01:00
Jason Wilder 433070a135 Add dashboards:export CLI command
Allows exporting all dashboards to a --dir or filtering dashboards
by title.  If no --dir is specified, a single dashboard must be
found and it will be sent to stdout which can be piped to a file.

Fixes #1498
2015-03-03 23:16:14 -07:00
Torkel Ödegaard d89c77af54 Small changes to build scripts 2015-03-04 07:09:59 +01:00
Torkel Ödegaard 2d7d70b90f removed old todo file 2015-03-03 21:36:51 +01:00
Torkel Ödegaard 5dc905e35d Updated wercker file 2015-03-03 21:11:37 +01:00
Torkel Ödegaard 660d44b6ad Fixed form issue with firefox and IE, Closes #1551 2015-03-03 20:47:35 +01:00
Torkel Ödegaard d3d39b3e17 s3 sync working finally 2015-03-03 20:35:42 +01:00
Torkel Ödegaard 3fd2f87b15 another wercker failure, trying this 2015-03-03 20:32:37 +01:00
Torkel Ödegaard 9e7a0f8d83 Updated wercker again 2015-03-03 20:26:39 +01:00
Torkel Ödegaard 6703f0514d Another attemp at s3 sync 2015-03-03 20:17:13 +01:00
Torkel Ödegaard 02f4eb53dd Updated wercker s3 step 2015-03-03 20:12:11 +01:00
Torkel Ödegaard 04597b3f54 Updated wercker test step 2015-03-03 20:08:20 +01:00
Torkel Ödegaard 4379f58213 Fixed intendation in wercker file 2015-03-03 19:59:53 +01:00
Torkel Ödegaard 2906d643a5 Testing s3sync from wercker build 2015-03-03 19:58:57 +01:00
Torkel Ödegaard bec9504cd8 Updated wercker file again 2015-03-03 19:17:24 +01:00
Torkel Ödegaard 7a8889e7f5 Added rpm tools to wercker pre build step 2015-03-03 19:00:22 +01:00
Torkel Ödegaard 8054f6a2d1 Updated build and wercker, again 2015-03-03 18:11:16 +01:00
Torkel Ödegaard f2595917c6 updated wercker build, trying to install ruby and fpm in wercker step 2015-03-03 18:00:58 +01:00
Torkel Ödegaard 55b40aeb65 updated build.go 2015-03-03 17:58:13 +01:00
Torkel Ödegaard 4f2bca08f1 Updated wercker file again... 2015-03-03 17:56:55 +01:00
Torkel Ödegaard 48d83a4dc4 Updated wercker file 2015-03-03 17:56:08 +01:00
Torkel Ödegaard 2362f9574e Updated wercker file 2015-03-03 17:50:33 +01:00
Torkel Ödegaard 8ba9b8d7bd added custom wercker box 2015-03-03 17:47:26 +01:00
Torkel Ödegaard c6d4b5cc86 Working on build steps 2015-03-03 17:42:50 +01:00
Torkel Ödegaard 0db55b6194 Work on deb and rpm packages, both seem to work now, #1476 2015-03-03 17:14:58 +01:00
Torkel Ödegaard 59da4a0b3b Updates to build and wercker file 2015-03-03 12:09:15 +01:00
Torkel Ödegaard 9192acf250 Fixed js style errors 2015-03-03 10:20:52 +01:00
Torkel Ödegaard 7e0f1a57af Progress on deb and rpm packaging, renamed config files, added file logging, #1476 2015-03-03 10:18:37 +01:00
Torkel Ödegaard f5cd3d853d Small update to confirm dialog 2015-03-03 08:03:21 +01:00
Torkel Ödegaard 04d25dc58a Dashboard: When saving a dashboard and another user has made changes inbetween, the user is promted with a warning if he really wants to overwrite the other's changes, Closes #718 2015-03-02 22:24:01 +01:00
Torkel Ödegaard 56c83cefe9 Fixed so that scripted dashboards and json file based dashboards works as in 1.x, Closes #1548 2015-03-02 18:26:43 +01:00
Torkel Ödegaard 6850a4d25d Progress on deb and rpm packaging, init.d script etc, #1476 2015-03-02 17:21:52 +01:00
Torkel Ödegaard ad2065afc7 Began work on deb and rpm packaging, #1476 2015-03-02 15:50:03 +01:00
Torkel Ödegaard 9710771f16 Added basic auth to data source edit/create, add support for basic auth in data source proxy code, Closes #1510 2015-03-02 09:58:35 +01:00
Torkel Ödegaard ea1164322b Small fixes 2015-03-02 08:33:06 +01:00
Torkel Ödegaard bee501da4a Added different time period override to singlestat panel, added option to hide panel time override info, Closes #171 2015-03-01 12:06:05 +01:00
Torkel Ödegaard 57b5b4c376 Worked on relative time and timeshift overrides for singlestat, still an issue for how to visualize the time override 2015-03-01 11:48:09 +01:00
Torkel Ödegaard 2c3d3d0fe3 Refactoring out common panel metric and query code to a panelHelper service 2015-03-01 11:03:41 +01:00
Torkel Ödegaard d0d995da09 Inital work on SQL metric/annotation data source, #1542 2015-02-28 17:27:30 +01:00
Torkel Ödegaard a3fe1efa2b Merge branch 'css-tweaks' into develop, #1465 2015-02-28 16:41:24 +01:00
Torkel Ödegaard e84d0dd6b3 Light theme tweaks after merge with bulletfactorys light theme fixes 2015-02-28 16:41:02 +01:00
Torkel Ödegaard 0f3bf488f2 Merge branch 'develop' into css-tweaks 2015-02-28 15:48:37 +01:00
Torkel Ödegaard 04ca85fe89 Moved dashboard theme option from the dashboard to a persisted user setting, #1458 2015-02-28 14:30:08 +01:00
Torkel Ödegaard 962b316bcf Some clean up and polish of data source edit view 2015-02-28 14:00:04 +01:00
Torkel Ödegaard ae3b9617b6 Fixed dataproxy test 2015-02-28 12:34:51 +01:00
Torkel Ödegaard 3c554953a2 Changed default OpenTSDB downsample aggregator from sum to avg 2015-02-28 12:33:20 +01:00
Torkel Ödegaard 0b1044b46f Fixed plugins go test 2015-02-28 11:38:44 +01:00
Torkel Ödegaard ba56535fa6 Merge branch 'light-theme-tweaks' of github.com:bulletfactory/grafana into css-tweaks
Conflicts:
	src/css/less/sidemenu.less
2015-02-28 11:37:38 +01:00
Torkel Ödegaard 2e0e8ba705 Fixed solo panel test 2015-02-28 11:15:21 +01:00
Torkel Ödegaard c283f0996d Fixed solo panel view (used for png rendering) 2015-02-28 11:07:22 +01:00
Torkel Ödegaard 232f980c72 More work on restoring features after moving to plugin model for datasources, no annotations work again #1276 #1472 2015-02-28 10:52:25 +01:00
Torkel Ödegaard 4a72c37fc1 Fixes to edit view switching, and to templateValuesSrv 2015-02-28 10:29:17 +01:00
Torkel Ödegaard dcfad7d61b Fixed playlist functionallity, broken after recent changes 2015-02-28 10:04:19 +01:00
Torkel Ödegaard 9e892bddf0 More work on restoring features after moving to plugin model for datasources, #1276 #1472 2015-02-28 09:46:37 +01:00
Torkel Ödegaard 109dd3240a Work on new datasource plugin model, #1276 #1472 2015-02-28 08:25:13 +01:00
Torkel Ödegaard c198242292 A lot of work on backend plugin model for frontend components, right now for data sources, will enable dropin plugins for data sources and panels, #1472 2015-02-27 22:29:00 +01:00
Torkel Ödegaard 5bd5713a52 Began work on plugin system 2015-02-27 13:45:00 +01:00
Trent White 5c39a3d004 Reversed button cleanup for light theme 2015-02-26 17:57:32 -05:00
Trent White 4e607876ea further cleanup of styles to make sidebar more readable for light theme. This tweaked link colors, dropdown colors and table tweaks for things like Add API to make them feel a little more organized through shades of gray. 2015-02-26 15:22:15 -05:00
Torkel Ödegaard 011fdf7ab6 fixed bug in datasource schema migration 2015-02-26 19:36:11 +01:00
Trent White 708ddf5387 CSS tweaks to link styles for sidebar, nav bar, search pane. Also the nav bar height seemed off from the sidebar, so made that consistent. Variables were created when colors were hard-coded for both dark and light. This meant that both variables.light.less and variables.dark.less were updated. 2015-02-26 12:37:35 -05:00
Torkel Ödegaard c75aa23092 New implementation for API Keys that only stores hashed api keys, and the client key is base64 decoded json web token with the unhashed key, Closes #1440 2015-02-26 17:23:28 +01:00
Torkel Ödegaard 6a2a6afc1d Merge branch 'develop' into apikey_hashed
Conflicts:
	pkg/api/apikey.go
	pkg/services/sqlstore/migrations.go
2015-02-26 15:48:54 +01:00
Torkel Ödegaard 7c241fd617 Added permissions section to admin > edit user view, an admin can now make another user admin, Closes #1517 2015-02-26 15:43:48 +01:00
Torkel Ödegaard c14a90a3d0 A lot of work on new organization selection and sidenav dropdown, #1506 2015-02-26 13:01:34 +01:00
Torkel Ödegaard 528f54153c More work on experimental InfluxDB 0.9 support 2015-02-26 08:55:49 +01:00
Torkel Ödegaard ae7f18f981 Made a copy of influxdb datasource named influxdb_08 so the main influxdb data source can be modified to support InfluxDB 0.9, made some initial experiments to get queries to work, but a lot more work is needed, #1525 2015-02-25 18:43:44 +01:00
Torkel Ödegaard f5f07bd552 Fixed signout links from sidenav 2015-02-25 16:52:57 +01:00
Torkel Ödegaard bbbd3320ef Created seperate admin sidenav #1506 2015-02-25 16:01:37 +01:00
Torkel Ödegaard 32450640e2 Moved clone dashboard above delete in dashboard settings dropdown 2015-02-25 14:32:57 +01:00
Torkel Ödegaard 3f96afba73 Merge branch 'fixDashboardDelete' of https://github.com/raintank/grafana into raintank-fixDashboardDelete
Conflicts:
	pkg/services/sqlstore/dashboard.go
2015-02-25 14:31:44 +01:00
Torkel Ödegaard 9e789e6d82 More work on organization / admin nav, views, #1506 2015-02-25 14:27:34 +01:00
Torkel Ödegaard e1d078f2c1 Final frontend changes for account -> org rename 2015-02-25 08:07:52 +01:00
Torkel Ödegaard 563d5e3ad8 All migrations work in sqlite3, mysql and postgres 2015-02-25 07:57:51 +01:00
Torkel Ödegaard 16fd256225 Added missing common.go 2015-02-24 19:10:41 +01:00
Torkel Ödegaard f3f79792ab account -> org table migration is starting to work, need to test mysql and postgres 2015-02-24 18:32:29 +01:00
Torkel Ödegaard ed68a4bb9a More work on SQL migrations 2015-02-24 17:59:21 +01:00
Torkel Ödegaard 02a89c752b Progress on database schema migration for account -> org refactor 2015-02-24 11:46:34 +01:00
Anthony Woods b9b63f695d fixes #1518 fix table name used in dashboard delete. 2015-02-24 08:56:10 +00:00
Torkel Ödegaard 7293ee0894 Changed default OpenTSDB downsample aggregator to avg, #1438 2015-02-24 09:54:02 +01:00
Torkel Ödegaard da41d99aa7 Fixed sql integration test 2015-02-23 20:48:43 +01:00
Torkel Ödegaard 26e4809e2e Big Backend Refatoring: Renamed Account -> Org 2015-02-23 20:07:49 +01:00
Torkel Ödegaard e9e2fa2927 Started Account -> Organization rename 2015-02-23 18:29:01 +01:00
Torkel Ödegaard 5b2715515f Began work on Account -> Organization rethink, #1506 2015-02-23 16:40:04 +01:00
Torkel Ödegaard a8c90e2365 Small update to commands 2015-02-23 14:20:24 +01:00
Torkel Ödegaard f66968ae02 Merge remote-tracking branch 'origin/master' into develop 2015-02-23 13:05:46 +01:00
Torkel Ödegaard a5fd40ed80 OpenTSDB: Fixed issue with auto interval variable going below 1s, Fixes #1325 2015-02-23 13:04:32 +01:00
Torkel Ödegaard 8cc4dae00b Merge remote-tracking branch 'origin/master' into develop 2015-02-23 12:57:12 +01:00
Torkel Ödegaard 2c5f19253c Added margin between opentsdb queries 2015-02-23 12:56:40 +01:00
Torkel Ödegaard 09f2950256 Merge remote-tracking branch 'origin/master' into develop
Conflicts:
	src/app/features/opentsdb/datasource.js
2015-02-23 12:50:58 +01:00
Torkel Ödegaard 65307c463a Refactoring opentsdb editor view, merging #1438, made Downsampling enabled by default 2015-02-23 12:48:07 +01:00
Torkel Ödegaard 2ae0ef94de Merge branch 'tsdb_downsample' of https://github.com/frogmaster/grafana into frogmaster-tsdb_downsample 2015-02-23 11:45:30 +01:00
Torkel Ödegaard 138e720695 Increased max user list to 1000 for user admin page, need to paging, but will have to wait a few days 2015-02-23 11:30:58 +01:00
Torkel Ödegaard 12e28c1895 added missing file 2015-02-23 11:24:45 +01:00
Torkel Ödegaard 1a106e5c38 Added change password ability to admin > edit user view, #1446 2015-02-23 11:24:22 +01:00
Torkel Ödegaard 15c52cacfb Import views are now consolidated into one view, and found through one navigation (dashboard search -> Import, Closes #1511 2015-02-23 10:50:50 +01:00
Torkel Ödegaard 4a93d205bb Moved dashboard import and json import to same view, found via dashboard search dropdown, #1511 2015-02-23 10:13:16 +01:00
Torkel Ödegaard a146a24c06 Fixed small issue in search and showing Home dashboard when filtering by tag 2015-02-23 09:54:26 +01:00
Torkel Ödegaard 6347b9b0d7 Merge remote-tracking branch 'origin/develop' into develop 2015-02-22 08:45:44 +01:00
Torkel Ödegaard fea6b970e5 Some prep work for using today as relative time, #1186 2015-02-22 08:44:37 +01:00
Torkel Ödegaard afeaa9fecd Merge pull request #1507 from ryan-williams/typos
typo fixes, .gitignore tweak
2015-02-22 08:31:24 +01:00
Torkel Ödegaard 7e82626b17 Fixed bug with custom time range not being initialized correctly, Fixes #1427 2015-02-22 08:25:44 +01:00
Torkel Ödegaard 9b9aab27ca Graphs now update/refresh when you edit annotations, Fixes #1430 2015-02-22 08:09:58 +01:00
Torkel Ödegaard 31e5271921 Fixed number formating issue when InfluxDB return MAX float64, Fixes #1402 2015-02-22 07:44:40 +01:00
Torkel Ödegaard 8c977e37a9 Merge remote-tracking branch 'origin/master' into develop
Conflicts:
	README.md
2015-02-21 21:22:09 +01:00
Torkel Ödegaard 9c8134f8bc Dashboard: Fixed memory leak when switching dashboards, Fixes #1497 2015-02-21 21:19:51 +01:00
Ryan Williams 36ec15be8f fix some scripted-dashboard-comment typos 2015-02-20 19:51:23 +00:00
Ryan Williams c29bf31f2b add intellij files to gitignore 2015-02-20 19:51:21 +00:00
Torkel Ödegaard 5b1727bcba Graph & Singlestat: Support for additional units, Fahrenheit (°F) and Celsius (°C), Humidity (%H), kW, watt-hour (Wh), kilowatt-hour (kWh), velocities (m/s, km/h, mpg, knot), Closes #1366 2015-02-20 14:45:00 +01:00
Torkel Ödegaard 7a14054057 Graph: Shared tooltip improvement, can now support metrics of different resolution/intervals, Closes #978, Fixes #1499 2015-02-20 14:07:24 +01:00
Torkel Ödegaard 481a4b0f1b Small fix to unsavedChangesSrv, did not handle new template variable correctly 2015-02-20 12:25:54 +01:00
Torkel Ödegaard fe34c8f2e0 Worked on submenu row (templating, annotations), removed templating and annotation feature toggles, the submenu row will be visible as soon as there are any template vars or annotations, #1503 2015-02-20 12:20:10 +01:00
Torkel Ödegaard 923e18b2b9 Small design change for the submenu (templating, annotations), #1503 2015-02-20 11:27:57 +01:00
Torkel Ödegaard 596dfc304d Added guard for template variables missing query field, Fixes #1501 2015-02-20 10:45:23 +01:00
Torkel Ödegaard 47c6c6e1b7 Added change password feature, Closes #1455 2015-02-19 16:09:49 +01:00
Torkel Ödegaard afc52f57a2 small fix to confirm modal 2015-02-19 10:42:13 +01:00
Torkel Ödegaard 0786977bcf Fixed css issue with sidebar 2015-02-19 10:15:24 +01:00
Torkel Ödegaard 181542249d Fixed failing influxdb-datasource test 2015-02-18 16:09:17 +01:00
Torkel Ödegaard 0d165e67c9 Fixed issue with influxdb, broken in recent commit today 2015-02-18 15:22:45 +01:00
Torkel Ödegaard 978b12b0dc Close dashboard edit views when clicking on dashboard title, added some tooltips to dashboard top nav 2015-02-18 15:17:31 +01:00
Torkel Ödegaard 2ca849c0c4 Changed docker image to debian:jessie 2015-02-18 14:43:58 +01:00
Torkel Ödegaard 3991d9dc06 Added error handling to dashboard imports, #1493 2015-02-18 14:18:54 +01:00
Torkel Ödegaard 60ae4afe87 Refresh frontend datasourceSrv after datasource update, no longer need to reload the page to use a newly added or updated datasource, #1493 2015-02-18 14:06:44 +01:00
Torkel Ödegaard 4ed54f6aa9 Force full reload when switching account, temp fix for refreshing frontend datasource, settings, and user account role 2015-02-18 13:18:29 +01:00
Torkel Ödegaard 17004ce3ae Fixed issue with login by username and uppercase letters in username, #1484 2015-02-18 13:00:39 +01:00
Torkel Ödegaard 596ce18aeb Worked on clone dashboard feature, #1488 2015-02-18 10:44:44 +01:00
Torkel Ödegaard 5c9ef9d9da Added form validation and css class for invalid timespans, #1494 2015-02-18 09:20:46 +01:00
Torkel Ödegaard ad13fd0542 Added validation for correct timespans when using panel timeshift or relative time override, Fixes #1494 2015-02-18 09:08:12 +01:00
Torkel Ödegaard 26eb6e559e Removed use of absolute urls in dashboard search and dashlist, root_url option is now not required, it is required if you use oauth or proxy grafana under suburl, #1483 2015-02-17 18:43:37 +01:00
Torkel Ödegaard 946afccbb5 Fixed dashboard search dropdown issue where it closes dashboard settings view if open, also fixed so that clicking outside dashboard search will close it, Fixes #1489 2015-02-17 18:20:47 +01:00
Torkel Ödegaard ea8307f79e Added influxdb docker block, and fixed issue when adding data source 2015-02-17 11:44:58 +01:00
Torkel Ödegaard 83d798dbad Small update to grafana.ini describe server options 2015-02-16 08:12:07 +01:00
Torkel Ödegaard 7f5f6763ea Fixed icon for ascending sort order in table legend, #1483 2015-02-16 07:52:16 +01:00
Torkel Ödegaard 8828db4fce Fixed issue with date range form validation, Closes #1485 2015-02-16 07:46:47 +01:00
Torkel Ödegaard 56d8fe4a22 Small changes to CLI commands PR 2015-02-16 07:40:21 +01:00
Jason Wilder b6428b08d0 CLI: Fix config flag being ignored
Passing --config had no effect when passed.  It will now be applied as
the last config file and before any env var overrrides.
2015-02-15 15:06:02 -07:00
Jason Wilder 9223c95481 CLI: Order commands alphabetically 2015-02-15 14:01:48 -07:00
Jason Wilder a3925e8aa0 CLI: Use console logger for dashbard:import command
More consistent w/ other commands and separates stdout/stderr
2015-02-15 13:51:41 -07:00
Jason Wilder 7c8fa067a2 CLI: Conver account flag to required arg
Follows the same convention as other commands.
2015-02-15 13:48:36 -07:00
Jason Wilder f2e9ec63df CLI: Renamed import-json to dashboard:import
More consistent w/ other command names.
2015-02-15 13:45:25 -07:00
Jason Wilder b97361b193 CLI: Add datasource:delete command 2015-02-15 13:36:10 -07:00
Jason Wilder 22652889b2 CLI: Add datasource:create command
Allows creating a datasource from the command line
2015-02-15 13:36:10 -07:00
Jason Wilder 04a970eda2 CLI: Use 8 char min col size instead of 20 2015-02-15 13:36:10 -07:00
Jason Wilder f443b7087c CLI: Add datasource:info command
Describes the full datasource details given an account and
datasource name.
2015-02-15 13:36:10 -07:00
Jason Wilder 9cb1170361 CLI: Move duplicated config flag to global flag 2015-02-15 13:36:10 -07:00
Jason Wilder dda760b9b5 CLI: Add datasource list command
Lists all the datasources for an account via the CLI
2015-02-15 13:36:10 -07:00
Jason Wilder ca4124940a CLI: Remove redundant Cmd prefix from commands 2015-02-15 13:36:10 -07:00
Jason Wilder 90cd10e034 CLI: Add account:delete command 2015-02-15 13:36:10 -07:00
Jason Wilder c1d4acc01e CLI: Use colorized console output
This extracts some of the colored logging functionality into some
convenience functions to log directly to the console (stdout) w/o
the usual logging prefixes and flags.  It's intended for console
messages when using grafana commands.
2015-02-15 13:36:10 -07:00
Jason Wilder 81531a29eb CLI: Add account:create command
Creates a new account attached to the default admin account for now.
2015-02-15 13:36:10 -07:00
Jason Wilder 7d4c319fcb CLI: Default logging output to stderr instead of stdout
Makes it possible to separate console output from logging output so
command output can be piped to a file cleanly.
2015-02-15 13:36:10 -07:00
Jason Wilder ca37b24455 CLI: Add account list command 2015-02-15 13:36:10 -07:00
Torkel Ödegaard f6c07fdabd Rewrote and redesign how the data source edit views look and work so they conform better to how account views look, removed tabs and put top nav items to add data source etc, made list, edit and new seperate url routes, #1483 2015-02-14 10:04:34 +01:00
Torkel Ödegaard ed0fabd9de Fixed css issue with sidenav not extending to bottom of page when dashboard requires scrolling, #1483 2015-02-14 09:05:35 +01:00
Torkel Ödegaard 0767992662 fixed z-index pos for panel-menu compared to dashboard search dropdown, #1483 2015-02-14 08:40:15 +01:00
Torkel Ödegaard a88187023d Added a sql integration test for api keys 2015-02-13 15:55:32 +01:00
Torkel Ödegaard 57dc12ee17 Merge pull request #1482 from raintank/issue_1481
fixes #1481.  correctly escape api_key.key column name
2015-02-13 15:47:12 +01:00
woodsaj c9e5da48a4 Merge branch 'issue_1481' of github.com:raintank/grafana into issue_1481 2015-02-13 22:00:57 +08:00
woodsaj 7d69885e06 fixes #1481. correctly escape api_key.key column name
'key' is a reserved word in mysql. So when building a query,
the api-key.key column name needs to be escaped
2015-02-13 22:00:03 +08:00
woodsaj 55ba8ad0c4 fix issue 1481. correctly escape api_key.key column name
'key' is a reserved word in mysql. So when building a query,
the api-key.key column name needs to be escaped
2015-02-13 21:52:56 +08:00
Torkel Ödegaard 6263ec1d38 Fixed some playlist issues, error when adding duplicates 2015-02-13 14:36:16 +01:00
Torkel Ödegaard 1d86d4b94f Updated docker build, added test container script, added docker readme 2015-02-13 14:18:37 +01:00
Torkel Ödegaard 8aef2c13ec Worked on playlist mode, hides dashboard controls and dashboard search button, added prev, stop, next icon buttons 2015-02-13 09:31:40 +01:00
Torkel Ödegaard 8722ee8ad6 Worked on playlist update for Grafana 2.0, added dashboard search to playliststart view, #1460 2015-02-13 08:47:44 +01:00
Torkel Ödegaard 1a44036148 Fixed req.Host in datasource proxy, Fixes #1478 2015-02-13 07:26:33 +01:00
Torkel Ödegaard e65a6cc063 added an inital admin settings view, very basic right now only displays all config options in grafana.ini 2015-02-12 15:46:14 +01:00
Torkel Ödegaard 29607d8951 Updated readme again 2015-02-12 13:45:53 +01:00
Torkel Ödegaard 366a9f71ad Updated readme build instructions 2015-02-12 13:44:22 +01:00
Torkel Ödegaard 79f798f67b Configuration file options can now be overriden using environment variables using GF_<SectionName>_<KeyName> syntax, if Section name contains dots in config they are replaced with underscores, and the section name and keyname needs to be all upper case, #1473 2015-02-12 13:31:41 +01:00
Torkel Ödegaard 2c16b0f0f3 added unit test for loading configuration file 2015-02-12 11:55:55 +01:00
Torkel Ödegaard 4df6668416 Merge branch 'develop' of github.com:grafana/grafana into develop 2015-02-12 10:34:52 +01:00
Torkel Ödegaard 0140a00884 Added two columns to user table, email_verified and theme, no used right now but will probably shortly 2015-02-12 10:32:22 +01:00
Torkel Ödegaard 2a31f16b76 Merge pull request #1467 from jwilder/jw-fixes
Small fixes to getting up and running
2015-02-12 02:26:54 +01:00
Jason Wilder 793eda7640 Assign new dashboard ID when importing dashboard via command-line 2015-02-11 15:20:36 -07:00
Jason Wilder 8230279932 Assign new ID to when importing dashboard via frontend
Fixes a panic: interface conversion: interface is string, not float64
when importing a dashboard that has a non-float ID.
2015-02-11 15:20:18 -07:00
Jason Wilder a6df991b76 Update README.md w/ known working build tool versions 2015-02-11 14:55:26 -07:00
Jason Wilder 9cc0be0fc2 Add grafana binary to .gitignore
Running "go build" defaults to building a binary named
grafana in the root dir.
2015-02-11 14:55:26 -07:00
Torkel Ödegaard 0d0d802526 Removed data folder stuff, should not be in repo 2015-02-11 21:21:21 +01:00
Torkel Ödegaard 71f09ddd94 Added delete user action to user admin api, and made it work in UI, Closes #1466, #1446 2015-02-11 16:47:22 +01:00
Torkel Ödegaard 17f1224a5d Fixed light theme condition, Fixes #1462 2015-02-11 15:32:19 +01:00
Torkel Ödegaard 441e2ad2f3 Updated dockerfile and moved to docker/production folder 2015-02-11 13:53:42 +01:00
Torkel Ödegaard 39a6bf9784 Updated readme with info about the Grafana 2.0 develop branch 2015-02-10 19:38:55 +01:00
Torkel Ödegaard 8d130421c4 Updated readme with default admin user info 2015-02-10 17:10:07 +01:00
Torkel Ödegaard add4adeec9 Fixed links to user admin views, and backend html routes for user admin edit/create urls, Closes #1451 2015-02-10 16:26:23 +01:00
Torkel Ödegaard e165e2af95 Worked on user admin features, can now create and edit users as a grafana admin user, #1446 2015-02-10 15:36:51 +01:00
Torkel Ödegaard 088ad881e0 Updated phantomjs panel rendering code 2015-02-10 11:13:35 +01:00
Torkel Ödegaard c5afcd8e09 Fixed issue with datasource proxying, it required account admin role when it should not 2015-02-10 10:19:43 +01:00
Torkel Ödegaard 5c795e9a50 Updated readme with docker info, updated build task 2015-02-09 16:18:30 +01:00
Torkel Ödegaard cd8f938cab Fixed issue with sidenav toggle on non dashboard pages 2015-02-09 16:02:42 +01:00
Torkel Ödegaard 2de6f3434c Fixed default '@timestamp' time field for elasticsearch annotations, Fixes #1444 2015-02-09 13:34:12 +01:00
Torkel Ödegaard 5269422f7c Began work on hashing api keys 2015-02-09 13:30:04 +01:00
Torkel Ödegaard c9f06e1da1 Fixed failing events test and issue with dashboard list panel 2015-02-08 11:24:03 +01:00
woodsaj c4fe9d50bf correctly handle event being a ptr
Events are passed in as PTRs, so we need to de-reference when getting the type and value.
2015-02-08 18:13:32 +08:00
Torkel Ödegaard 2c5828528f Updates to account and admin views 2015-02-08 10:23:35 +01:00
Torkel Ödegaard b1a95cacff Small css changes 2015-02-08 09:38:05 +01:00
Torkel Ödegaard 20b64d9b7e PanelSrv fix, now panel rerenders after column span change 2015-02-08 09:02:03 +01:00
Torkel Ödegaard ab55412264 Refactoring of grafana (root scope view model), into contextSrv, made sidemenu always hide after login 2015-02-08 09:00:58 +01:00
Torkel Ödegaard f82cb3aefe Various css updates 2015-02-07 22:00:33 +01:00
Torkel Ödegaard 48e5a82e7f Reduced height of search view 2015-02-07 20:43:20 +01:00
Torkel Ödegaard 727fc9d3a6 Updated search result view, removed delete link, added delete action from dashboard cogs dropdown 2015-02-07 20:25:53 +01:00
Torkel Ödegaard 51c640d409 Updated search result padding 2015-02-07 16:19:02 +01:00
Torkel Ödegaard 7ab83bf47f Updated position of search 2015-02-07 16:16:17 +01:00
Torkel Ödegaard 9818f81c5f Completed search makover 2015-02-07 16:12:29 +01:00
Torkel Ödegaard 8f4e440179 Working on new search box design 2015-02-07 15:40:31 +01:00
Torkel Ödegaard 83bf68ecad Added border for inverse button 2015-02-07 14:10:51 +01:00
Torkel Ödegaard 23bbc5160e Fixed validation for adding api keys, added unique index for api key name + account_id 2015-02-07 13:11:39 +01:00
Torkel Ödegaard ed6d50d6ba Updated to profile page design 2015-02-07 13:04:24 +01:00
Torkel Ödegaard b3a1bb2017 Sub nav for account page, some css changes 2015-02-07 12:28:47 +01:00
Teet Talviste a0a4b04007 fix identation 2015-02-06 21:15:14 +02:00
Teet Talviste ddfb36b511 satisfy travis: fix curly brace space 2015-02-06 21:07:31 +02:00
Teet Talviste 28f7806d19 fix some linting problems 2015-02-06 18:42:12 +02:00
Teet Talviste 72f6dc1e0c enable sending calculated interval to opentsdb downsample 2015-02-06 18:42:12 +02:00
Torkel Ödegaard 9a45a25e17 Removed inner black border for dashboard button 2015-02-06 17:24:19 +01:00
Torkel Ödegaard 9cadf0aa97 Updated phantomjs render script and wercker link in readme 2015-02-06 14:22:56 +01:00
Torkel Ödegaard 397dfc54cd Fixed bug in png rendering when having changed public url 2015-02-06 14:17:40 +01:00
Torkel Ödegaard eeba128f8a Fixed release tar/zip keeping execution mode on phantomjs 2015-02-06 13:58:25 +01:00
Torkel Ödegaard 396b687e4b Added docker file for docker registry distribution 2015-02-06 11:40:04 +01:00
Torkel Ödegaard cbe72ba9f4 Updated wercker file 2015-02-06 10:19:40 +01:00
Torkel Ödegaard 85c1c97ea2 Updated readme 2015-02-06 09:10:52 +01:00
Torkel Ödegaard 503e04d375 Updated readme 2015-02-06 08:54:43 +01:00
Torkel Ödegaard 4e5d5cd1a9 updated readme 2015-02-06 08:52:16 +01:00
Torkel Ödegaard 9cdd9e6d5f updated readme 2015-02-06 08:40:12 +01:00
Torkel Ödegaard f8a10fa7af Updated account and profile pages, very temporary solution, do not like it at all 2015-02-06 08:21:00 +01:00
Torkel Ödegaard e3764ad951 Fixed failing dashboard sql test 2015-02-05 15:53:15 +01:00
Torkel Ödegaard edb7722ad6 Updated wercker file again 2015-02-05 15:29:06 +01:00
Torkel Ödegaard 39ee394068 updated wercker file to change package dir 2015-02-05 15:28:17 +01:00
Torkel Ödegaard f4e26bba3f Removed unused node package 2015-02-05 12:39:47 +01:00
Torkel Ödegaard 102c896046 Updated build and wercker file for new repo structure 2015-02-05 12:27:58 +01:00
Torkel Ödegaard e5fbbe96fd fixed error handling, and error logging for panel rendering 2015-02-05 12:23:24 +01:00
Torkel Ödegaard 8e1b753664 Added limit to dashboard list panel and search 2015-02-05 11:10:56 +01:00
Torkel Ödegaard b6d5f49c0f Merge branch 'master' of github.com:torkelo/grafana-private into develop 2015-02-05 10:56:59 +01:00
Torkel Ödegaard 2cc9515fd6 Added missing mega bytes unit format to unit selection, #999 2015-02-05 10:56:39 +01:00
Torkel Ödegaard 0f63c04beb Fixed bug in user creation and admin role 2015-02-05 10:50:18 +01:00
Torkel Ödegaard 861e45aedf Merge remote-tracking branch 'origin/pro' 2015-02-05 10:37:41 +01:00
Torkel Ödegaard 10820f31c2 Changed go package path 2015-02-05 10:37:13 +01:00
Torkel Ödegaard 3ab7b5c3f7 Merge branch 'master' of /home/torkel/dev/go/src/github.com/torkelo/backend
Conflicts:
	.gitignore
	LICENSE.md
	README.md
2015-02-05 10:10:39 +01:00
Torkel Ödegaard 076905d14e Added isStarred to search result hit, very inefficient loading right now but can be cached later on 2015-02-05 09:49:00 +01:00
Torkel Ödegaard d5471c153a Worked on dashlist panel, can now do searched 2015-02-05 09:48:14 +01:00
Torkel Ödegaard 69e7279cff fixed unit test failing on build server 2015-02-04 18:25:15 +01:00
Torkel Ödegaard 9e58921b23 Updated godeps file 2015-02-04 18:06:37 +01:00
Torkel Ödegaard 7fa6527ca8 Updated godeps file 2015-02-04 18:06:30 +01:00
Torkel Ödegaard 2fa8096569 Updated frontend 2015-02-04 17:21:57 +01:00
Torkel Ödegaard ff47eccf4b Fixed javascript style checker issue 2015-02-04 17:21:42 +01:00
Torkel Ödegaard 5a6c04e3b0 frontend fixes 2015-02-04 17:20:38 +01:00
Torkel Ödegaard 896e6d4662 Merge branch 'notifications' 2015-02-04 17:15:16 +01:00
Torkel Ödegaard d8db5189c1 More work on events, still have to convert pascal case event type name to rabbitmq dot notation, but after that should be done 2015-02-04 17:15:05 +01:00
Torkel Ödegaard 525179eb85 Added on wire event format 2015-02-04 16:57:20 +01:00
Torkel Ödegaard dace35d31d Missed setting account name 2015-02-04 15:41:40 +01:00
Torkel Ödegaard 3752379106 Worked on event system, needs a little more work 2015-02-04 15:37:26 +01:00
Torkel Ödegaard 048b26a0fd Updated home dashboard 2015-02-04 11:50:40 +01:00
Torkel Ödegaard 07ec00641f update home dashboard 2015-02-04 11:50:22 +01:00
Torkel Ödegaard 60541a455f Worked on search filter flag IsStarred, and updated frontend with new dashboard list panel 2015-02-04 11:35:59 +01:00
Torkel Ödegaard 882ee4d49f A start on a dashboard list panel, can now list starred dashboards 2015-02-04 11:35:19 +01:00
Torkel Ödegaard f0b13153ac fixed bug in frontend 2015-02-04 08:20:24 +01:00
Torkel Ödegaard df51be02bf Fixed loading path for home dashboard 2015-02-04 08:20:00 +01:00
Torkel Ödegaard 96798ac70c Updated 2015-02-04 07:01:15 +01:00
Torkel Ödegaard a28e4de3bd added fade transition for logo icon to hamburger 2015-02-04 07:01:03 +01:00
Torkel Ödegaard ac73e78b3a Fixed unit test in frontedn 2015-02-03 22:18:38 +01:00
Torkel Ödegaard 613e209af3 Fixed solo panel unit test 2015-02-03 22:18:16 +01:00
Torkel Ödegaard 1ac3355b03 updated frontend with style changes 2015-02-03 22:06:07 +01:00
Torkel Ödegaard 5e4b026668 Css work applying new styles for topmenu dashboard button and logo/menu icon 2015-02-03 22:05:53 +01:00
Torkel Ödegaard 9d5979c01d Moved dashboards dir and started work on new panel for dashboard lists 2015-02-03 20:50:52 +01:00
woodsaj a712f1a231 Add inital implementation of Notification events.
If notifications are enabled in the config, Adds a eventHandler
accepting Notification{} payloads to the internal Bus.  The
eventHandler then marshals the payload into json and sends it
to a rabbitmq topic exchange using the
Notification.Priority+Noticiation.EventType as the routing key.
eg.  INFO.account.created

Currently, notifications are only being emitted for
INFO.account.created
INFO.account.updated
INFO.user.created
INFO.user.updated
2015-02-03 23:57:42 +08:00
Torkel Ödegaard 42a5aefb23 Worked on home dashboard 2015-02-03 15:04:35 +01:00
Torkel Ödegaard bc673fd969 Worked on loading a home dashboard through backend 2015-02-03 15:04:18 +01:00
Torkel Ödegaard a127f2d572 Fixed png rendering 2015-02-03 13:45:52 +01:00
Torkel Ödegaard 16acdfc7c3 Merge branch 'master' of github.com:torkelo/grafana-private into pro 2015-02-03 13:36:25 +01:00
Torkel Ödegaard 858d7be8cf Migrated from ngmin to ngAnnotate 2015-02-03 13:36:04 +01:00
Torkel Ödegaard 1ca4ecc241 Updated grunt-jscs dependency version 2015-02-03 13:05:14 +01:00
Torkel Ödegaard bd470abcf0 fontend styles updated 2015-02-03 10:46:52 +01:00
Torkel Ödegaard dfef4727b6 Updated to sidemenu style 2015-02-03 10:46:33 +01:00
Torkel Ödegaard 3e5223b98d Merge branch 'master' of github.com:torkelo/grafana-private into pro 2015-02-03 09:43:21 +01:00
Torkel Ödegaard 0fe83d5198 minor css tweaks 2015-02-03 09:43:07 +01:00
Torkel Ödegaard 6b6b97c8d8 Minor bug fix for rememeber refresh interval after zooming two or more times, Fixes #1408 2015-02-03 09:27:00 +01:00
Torkel Ödegaard 5c3e366192 Fixed wrong icon class in annotations, broken after recent upgrade of font-awesome, Fixes #1416 2015-02-03 09:18:00 +01:00
Torkel Ödegaard 68a6a1bc15 Updated frontend, fix for paths to partials 2015-02-02 22:15:45 +01:00
Torkel Ödegaard 7fef460fa2 Fix for paths to partials 2015-02-02 22:15:15 +01:00
Torkel Ödegaard 4b7d802a9d Frontend updated with a big panel refactoring 2015-02-02 21:59:24 +01:00
Torkel Ödegaard 04ec222462 Big refactoring of the way panels are loaded and how they are built using directives, this way feel cleaner and allowed for placing the panel edit box outside the panel-container 2015-02-02 21:59:04 +01:00
Torkel Ödegaard 97758380e0 Worked on stars in search results 2015-02-02 17:17:57 +01:00
Torkel Ödegaard b67f4dc390 Added stars to search results 2015-02-02 17:17:30 +01:00
Torkel Ödegaard ad3d15e28d Worked on dashboard starring and unstarring, renamed favorite model to star 2015-02-02 11:32:32 +01:00
Torkel Ödegaard 1e3970c6e5 Worked on dashoard starring and unstarring, added dashboardMeta model 2015-02-02 11:32:00 +01:00
Torkel Ödegaard 706481b1a3 Updated frontend, fixed dashboard share feature 2015-02-02 09:46:03 +01:00
Torkel Ödegaard 66d9c4f1af Fixed share dashboard feature, sharing temp feature is currently removed 2015-02-02 09:45:45 +01:00
Torkel Ödegaard cd17278c64 Updated frontend, a lot of markup, css fixes, restoring features 2015-02-02 09:00:04 +01:00
Torkel Ödegaard f424abf87c more markup fixes 2015-02-02 08:59:38 +01:00
Torkel Ödegaard 8a5c91021d Various css and markup fixes for changed css classes 2015-02-02 08:34:54 +01:00
Torkel Ödegaard ddc32d3cad updated frontend 2015-02-01 20:24:41 +01:00
Torkel Ödegaard 26535e163f fixing broken things in the markup for changed css classes 2015-02-01 20:23:52 +01:00
Torkel Ödegaard cefdd86634 Minor css changes to graph panel & full edito mode 2015-02-01 19:59:53 +01:00
Torkel Ödegaard 56aa8ae6e0 more design work 2015-02-01 19:13:33 +01:00
Torkel Ödegaard aa613bc767 updated frontend 2015-02-01 15:46:24 +01:00
Torkel Ödegaard d272b0f56d Merge branch 'new_top_menu_design' into pro
Conflicts:
	src/app/partials/sidemenu.html
2015-02-01 15:46:08 +01:00
Torkel Ödegaard 6bc524dff8 progress on new design 2015-02-01 15:45:11 +01:00
Torkel Ödegaard 496e5bdad9 More progress on new design 2015-02-01 11:56:01 +01:00
Torkel Ödegaard f00e5936c9 New design work 2015-01-31 18:00:26 +01:00
Torkel Ödegaard 6ea35fce66 Updated frontend 2015-01-30 15:10:43 +01:00
Torkel Ödegaard 3da90e09d5 Fixed logout link 2015-01-30 15:10:22 +01:00
Torkel Ödegaard 20c6925470 Fixed sub url setting bug 2015-01-30 14:21:32 +01:00
Torkel Ödegaard 465c72d98e More work on topnav and gf-box redesign 2015-01-30 14:12:16 +01:00
Torkel Ödegaard 59ade61687 dashboard-edit-view -> gf-box and new box design 2015-01-30 12:57:14 +01:00
Torkel Ödegaard 1ddc2e68bf Further progress on new design 2015-01-30 11:34:54 +01:00
Torkel Ödegaard fcba5a6eeb More progress on new design 2015-01-30 10:25:10 +01:00
Torkel Ödegaard 2f7770b165 Work on new top nav design 2015-01-30 09:47:16 +01:00
Torkel Ödegaard 04d03f73b3 Added disable user sign up feature 2015-01-29 15:46:54 +01:00
Torkel Ödegaard 740709da04 Added disable user sign up feature 2015-01-29 15:46:36 +01:00
Torkel Ödegaard 2feab76a6f Merge branch 'pro' into temp_test 2015-01-29 15:08:53 +01:00
Torkel Ödegaard d95c5e6674 Basic import of json dashboards is working, needs more work to handle updates, and continous watching, #22 2015-01-29 14:33:50 +01:00
Torkel Ödegaard 1d6413bfae More work on backend for user favorites 2015-01-29 12:10:34 +01:00
Torkel Ödegaard 3b5c813be7 worked on user frontend state, state like favorites, etc 2015-01-29 12:10:14 +01:00
Torkel Ödegaard e02e60171e Began work on user favorites backend support & storage 2015-01-28 15:29:36 +01:00
Torkel Ödegaard b25bf363b3 updated frontend, fixed new dashboard button 2015-01-28 14:31:09 +01:00
Torkel Ödegaard 9d0982f2f7 Merge branch 'master' of github.com:torkelo/grafana-private into pro
Conflicts:
	src/app/directives/dashEditLink.js
2015-01-28 14:30:16 +01:00
Torkel Ödegaard 25316cbe0e Fixed broken feature: new dashboard button 2015-01-28 14:25:35 +01:00
Torkel Ödegaard cf5595bd64 mini fix for submenuEnabled being undefined, when initiating a dashboard with empty model 2015-01-28 14:24:11 +01:00
Torkel Ödegaard 12f2ca9262 updated build file 2015-01-28 13:10:09 +01:00
Torkel Ödegaard 5631d52f50 Added admin settings view 2015-01-28 11:39:35 +01:00
Torkel Ödegaard a7a8ea3077 added admin settings view 2015-01-28 11:39:22 +01:00
Torkel Ödegaard 1cff564483 Fontend handling of account role to hide user actions and links that the user does not have access to 2015-01-28 11:33:50 +01:00
Torkel Ödegaard c75e669204 Hide sidemenu links that the user does not have access to 2015-01-28 11:33:12 +01:00
Torkel Ödegaard 1758412a76 Settings fix 2015-01-28 10:54:14 +01:00
Torkel Ödegaard aa261bbe23 Login: only enabled oauth options are shown on login page 2015-01-28 10:26:13 +01:00
Torkel Ödegaard 017eab8dcd Login: only enabled oauth options are now shown on login page 2015-01-28 10:25:53 +01:00
Torkel Ödegaard 7e26d7a4bf Fix to dashboard loading and error handling 2015-01-28 09:58:24 +01:00
Torkel Ödegaard c6e110100a Just testing a thing 2015-01-27 18:20:12 +01:00
Torkel Ödegaard a5e450a0dd Worked on anonymous access 2015-01-27 15:45:27 +01:00
Torkel Ödegaard 757b185398 Worked on ease of use for non multi tenant scenarios, Closes #20 2015-01-27 15:14:53 +01:00
Torkel Ödegaard 257519490a Worked on login remember cookie, and redirect after login 2015-01-27 12:05:23 +01:00
Torkel Ödegaard 4572747bd6 Worked on login remember cookie and redirect after login 2015-01-27 11:59:20 +01:00
Torkel Ödegaard 6e5ef561eb Updated Godeps 2015-01-27 10:11:18 +01:00
Torkel Ödegaard 95305e7e11 Changed from goconfig to its new counter part go-ini 2015-01-27 10:09:54 +01:00
Torkel Ödegaard 951ce0a102 API token -> API key rename 2015-01-27 08:26:11 +01:00
Torkel Ödegaard 11b74baf79 Api token -> api key rename 2015-01-27 08:25:52 +01:00
Torkel Ödegaard c0353ab5d8 Graph: fix for legend show/hide toggle, after hiding requires new data fetch to show, Fixes #1393 2015-01-27 07:56:23 +01:00
Torkel Ödegaard 9fa0a4c973 Dashboard: fixed scroll isssue in firefox after dashboard import, Closes #1391 2015-01-27 07:47:01 +01:00
Torkel Ödegaard db371d2a5d API: added admin role requirement for account changes, datasource admin, and api keys admin 2015-01-26 20:26:17 +01:00
Torkel Ödegaard 01cce09ef2 Updated frontend 2015-01-26 16:00:03 +01:00
Torkel Ödegaard d2f21bc93e Merge branch 'master' into pro 2015-01-26 15:56:39 +01:00
Torkel Ödegaard 5ba2423626 Worked on account update view 2015-01-26 15:42:22 +01:00
Torkel Ödegaard a6b0856020 Worked on account update view 2015-01-26 15:42:11 +01:00
Torkel Ödegaard 9a641ee7c0 Corrected the use of POST vs PUT, POST creates something, PUT updates, got them mixed up 2015-01-26 12:58:03 +01:00
Torkel Ödegaard ab7e2f89fb Corrected the use of POST vs PUT, POST creates something, PUT updates, got them mixed up 2015-01-26 12:57:44 +01:00
Torkel Ödegaard 71ab8d6afc Merge branch 'master' of github.com:torkelo/grafana-private 2015-01-26 12:41:15 +01:00
Torkel Ödegaard ea5da627af Panel: Different time periods, panels can override dashboard relative time and/or add a time shift, #171, only works for graph panel for now, need feedback for how to do it for singlestat panel 2015-01-26 11:49:18 +01:00
Torkel Ödegaard 1c8ef716a2 Began work on time overrides for panels, to enable different time periods on the same dashboard, #171 2015-01-26 10:57:08 +01:00
Torkel Ödegaard 6fc451da9e Small fix 2015-01-25 14:51:51 +01:00
Torkel Ödegaard b940f4a97d Timepicker: New option in timepicker (under dashboard settings), to change now to be for example now-1m, usefull when you want to ignore last minute because it contains incomplete data, Closes #1242, Closes #374 2015-01-25 13:45:08 +01:00
Torkel Ödegaard c2c81e7e6a Graph: Fix for all series tooltip showing series with all null values when Hide Empty option is enabled, Fixes #1359 2015-01-23 11:36:18 +01:00
Torkel Ödegaard 1508d0ac23 TemplatOCing: Fix to allow custom template variables to contain white space, now only splits on ',', Fixes #1363 2015-01-23 10:41:23 +01:00
Torkel Ödegaard d3c37bda71 Graphite: Fix for nested complex queries, where a query references a query that references another query (ie the #[A-Z] syntax), Fixes #1372 2015-01-23 10:29:54 +01:00
Torkel Ödegaard c82e2d74bf Login validation 2015-01-22 11:51:44 +01:00
Torkel Ödegaard 1a11b400b0 Polish and incorporating new design for login screen from matt 2015-01-22 10:14:29 +01:00
Torkel Ödegaard 1d8bb45d3a quickfix for support suburl for sidemenu nav 2015-01-21 20:14:53 +01:00
Torkel Ödegaard fc9de68ee9 fixed broken html on login page 2015-01-21 20:01:22 +01:00
Torkel Ödegaard 1fb9bbba91 a lot of work on sidemenu, and encoding dash edit view in url 2015-01-21 19:45:23 +01:00
Torkel Ödegaard cd4fc78aec trying to get dashboard settings, annotations and templating views be opened via url parameters 2015-01-21 16:29:04 +01:00
Torkel Ödegaard 4e6a04923a worked on new sidemenu design 2015-01-21 14:44:34 +01:00
Torkel Ödegaard 83899eb884 added hamburger 2015-01-21 13:05:55 +01:00
Torkel Ödegaard 3744bc3228 fixed server side rendering 2015-01-21 10:51:42 +01:00
Torkel Ödegaard a0c8d3fa6f Small fix to data source view 2015-01-21 10:51:28 +01:00
Torkel Ödegaard d3c13a1d8c removed old signup/register view 2015-01-21 09:55:15 +01:00
Torkel Ödegaard 3c8c53194f Sign up and login work 2015-01-21 09:52:40 +01:00
Torkel Ödegaard 8ed9212401 Redesigned and polished login view, still needs work on validation 2015-01-21 09:51:31 +01:00
Torkel Ödegaard 5cef73331c Fixed asteric icon in graphite metric path selector, broken after resent font awesome upgrade, #1361 2015-01-21 07:27:27 +01:00
Torkel Ödegaard 5760fa104c trying to cleanup login page 2015-01-21 06:41:09 +01:00
Torkel Ödegaard 4786e0f882 Changed name for vendor folder 2015-01-20 17:53:20 +01:00
Torkel Ödegaard 3bf7f6ed5f Updated build post processsing to include vendor dir 2015-01-20 17:53:01 +01:00
Torkel Ödegaard 9e6df378c3 worked on account creation 2015-01-20 16:29:48 +01:00
Torkel Ödegaard 9667f324f1 added ability to create accounts from profile page 2015-01-20 16:29:19 +01:00
Torkel Ödegaard eec178458b Validation check for not removing the last account admin 2015-01-20 15:48:19 +01:00
Torkel Ödegaard 4ea5d80099 Various fixes to data access 2015-01-20 15:23:14 +01:00
Torkel Ödegaard d9a33680a6 Fixes to account/account users 2015-01-20 15:22:45 +01:00
Torkel Ödegaard 0a695ba17a Final work on migration, now there is no usage of xorm table sync 2015-01-20 14:44:37 +01:00
Torkel Ödegaard afb847acc8 a lot of work on database schema and migration setup, postgres now works, every integration test passes for all database types, only token table left to do 2015-01-20 14:15:48 +01:00
Torkel Ödegaard 8bb9126b77 added postgres support for db migrations 2015-01-20 09:20:44 +01:00
Torkel Ödegaard 2379c5b770 Added loglevel to migrator, added dashboard table & index migrations 2015-01-20 08:50:08 +01:00
Torkel Ödegaard 581efa857b Small fixes 2015-01-19 19:10:29 +01:00
Torkel Ödegaard 590c3b4b50 Fixes to account users view 2015-01-19 19:10:04 +01:00
Torkel Ödegaard e750508d76 Fixes for update user and add token 2015-01-19 18:57:51 +01:00
Torkel Ödegaard efe7279ead Quick fix for frontend for account to user split 2015-01-19 18:57:25 +01:00
Torkel Ödegaard 90925273a0 User / Account model split, User and account now seperate entities, collaborators are now AccountUsers 2015-01-19 18:01:04 +01:00
Torkel Ödegaard f1996a9f1f Initial work on seperation between user and account 2015-01-19 16:28:45 +01:00
Torkel Ödegaard e411b8e423 Fixed icons in graph tooltip, not updated after recent font awesome upgrade, Closes #1355 2015-01-19 13:08:18 +01:00
Torkel Ödegaard d8e5be5782 Worked on database agnostic table creation for db migrations 2015-01-19 10:44:16 +01:00
Torkel Ödegaard 7d70ffe201 adding more columns for account table migration 2015-01-18 20:09:30 +01:00
Torkel Ödegaard a64a38d7dd Added migration log and migration id, do not execute already executed migrations 2015-01-18 18:41:03 +01:00
Torkel Ödegaard 8bfed7508c More work on sql schema and migrations, starting to get somewhere 2015-01-18 14:51:51 +01:00
Torkel Ödegaard 68a77c4051 More progress on db schema setup and migrations, tricky stuff 2015-01-18 13:08:59 +01:00
Torkel Ödegaard 38f237efcb Merge branch 'master' of github.com:torkelo/grafana-pro 2015-01-18 08:56:32 +01:00
Torkel Ödegaard 2daffe2a15 Api import route updated 2015-01-18 08:56:19 +01:00
Torkel Ödegaard 1f987c1903 Began work on real sql schema definitions, and migration engine 2015-01-17 21:40:22 +01:00
Torkel Ödegaard 9a29b04561 Dashboard import/export now works 2015-01-17 10:39:26 +01:00
Torkel Ödegaard 1c5f902770 Added dashboard import feature 2015-01-17 10:39:01 +01:00
Torkel Ödegaard 3be11b9861 Small cosmetic change 2015-01-17 08:27:55 +01:00
Torkel Ödegaard c970e82758 Small update to update account command 2015-01-17 08:20:25 +01:00
Torkel Ödegaard a0036179d5 Removed alerts and graph from sidemenu, added required field to account input fields 2015-01-17 08:19:45 +01:00
Torkel Ödegaard 1532eb4278 Fixed png rendering 2015-01-16 17:00:31 +01:00
Torkel Ödegaard 64f98d2409 Mini fix for sidemenu 2015-01-16 17:00:02 +01:00
Torkel Ödegaard ed879df1f4 Corrected spelling of SignedInUser (was SignInUser) 2015-01-16 16:17:35 +01:00
Torkel Ödegaard 2b05dac071 Api Key role is now correcty added do middleware context 2015-01-16 16:15:35 +01:00
Torkel Ödegaard 507bff8b59 Godep is acting strangly 2015-01-16 15:48:34 +01:00
Torkel Ödegaard 3912ed5023 Role checking when saving dashboard, making sure that the user has owner or editor role 2015-01-16 15:28:44 +01:00
Torkel Ödegaard 984ece646f Get access denied when trying to save from collaboration role viewer 2015-01-16 15:21:07 +01:00
Torkel Ödegaard 22156fe309 Big refactoring for context.User, and how current user info is fetching, now included collaborator role 2015-01-16 14:32:18 +01:00
Torkel Ödegaard 9637efd5ee renamed isAdmin to isGrafanaAdmin to make it less confusable with admin role 2015-01-16 14:31:09 +01:00
Torkel Ödegaard 52992928d5 Updated macaron dependency 2015-01-16 12:37:25 +01:00
Torkel Ödegaard aa9ca68883 Tokens small fixes 2015-01-16 12:28:35 +01:00
Torkel Ödegaard aaa717aac2 Refactoring apikeys into its own view 2015-01-16 12:28:09 +01:00
Torkel Ödegaard 4ece7fc038 Merge remote-tracking branch 'origin/tokens' into pro 2015-01-16 12:07:27 +01:00
Torkel Ödegaard 9feb8a73fd Added binding to tokens api and role validation 2015-01-16 12:06:24 +01:00
Torkel Ödegaard d3cc6e518d updated collaborators 2015-01-16 12:06:05 +01:00
Torkel Ödegaard f858f6b621 Add collaborator now handles role, added macaron-contrib/binding for binding and validation 2015-01-16 11:54:19 +01:00
Torkel Ödegaard 500e000661 Add collaborator now handles roles, added macaron-binding for binding and validation 2015-01-16 11:53:31 +01:00
Torkel Ödegaard 4c73d970e5 Merge branch 'master' of github.com:torkelo/grafana-private into pro 2015-01-16 09:32:06 +01:00
Torkel Ödegaard becdaafdca Refactored away the graphite-target-inner / tight-form-row 2015-01-16 09:31:38 +01:00
woodsaj 3887aa72cb add tokens to UI under the Account Settings.
Adds support for adding and removing API tokens.
2015-01-16 16:04:46 +08:00
Torkel Ödegaard 72d7a7d91d changed grafana-target css names in partials 2015-01-16 08:50:26 +01:00
Torkel Ödegaard e1f410d32e Starting css refactor for grafana-target 2015-01-16 08:35:32 +01:00
Torkel Ödegaard cd5843e977 Fixed unit tests 2015-01-16 07:45:37 +01:00
Torkel Ödegaard af3aa81d71 UI: Fixed position of confirm modal when scrolled down, Fixes #1345 2015-01-16 07:20:00 +01:00
Torkel Ödegaard 04bbdbad12 Worked on account update, moved collaborats to its own api url and files 2015-01-15 19:14:47 +01:00
Torkel Ödegaard 9d1dacb8d4 Worked on account update, moved collaborators to its own view 2015-01-15 19:14:07 +01:00
Torkel Ödegaard f3bb2d41a7 removed sidemenu controller 2015-01-15 17:41:28 +01:00
Torkel Ödegaard 804bff55ec Accounts admin view/get api 2015-01-15 15:54:22 +01:00
Torkel Ödegaard 5b93e09714 Very basic start for accounts admin view 2015-01-15 15:53:55 +01:00
Torkel Ödegaard fdfcc3ab2a Admin flagged users, create a default admin user on startup if missing 2015-01-15 14:44:15 +01:00
Torkel Ödegaard cf344abff2 Admin menu only visible for admins 2015-01-15 14:43:44 +01:00
Torkel Ödegaard 5ec07db143 Refactoring of auth middleware, and starting work on account admin 2015-01-15 12:16:54 +01:00
Torkel Ödegaard 67b935188b Fixed failing unit test 2015-01-15 10:52:22 +01:00
Torkel Ödegaard 0dfd09ed0e fixed build.go setup 2015-01-14 20:51:22 +01:00
Torkel Ödegaard 6f63d63ee0 Working on account collaborators 2015-01-14 16:12:37 +01:00
Torkel Ödegaard 961ebbde6b Working on account collaborators 2015-01-14 16:12:19 +01:00
Torkel Ödegaard 50a5355de0 Merge pull request #17 from torkelo/tokenGetAccountFix
fix getAccountByToken query.
2015-01-14 14:59:23 +01:00
woodsaj f25cffd24e fix getAccountByToken query. 2015-01-14 21:45:00 +08:00
Torkel Ödegaard 5e18afe916 Refactoring of api routes 2015-01-14 14:25:12 +01:00
Torkel Ödegaard 733a9af629 small changes 2015-01-14 14:24:54 +01:00
Torkel Ödegaard 166ce7d2ae Added Gzip option and the macaron Gzip middleware, but does not seem to work 2015-01-14 10:34:14 +01:00
Torkel Ödegaard 5833867b44 renamed register route to signup 2015-01-14 10:19:53 +01:00
Torkel Ödegaard 6cc1502c89 Renamed register routes/ctrl to signup 2015-01-14 10:19:35 +01:00
Torkel Ödegaard ced5e5500e mini code cleanup of in auth 2015-01-14 10:14:07 +01:00
woodsaj 7b17e38f5d add Token authentication support
Added CRUD methods for Tokens.
Extend Auth Handler to check for the presence of a Bearer Authorization
header to authenticate against. If there is no header, or the token is not
valid, the Auth Handler falls back to looking for a Session.
2015-01-14 16:33:34 +08:00
Torkel Ödegaard e58cd91487 updated frontend 2015-01-13 14:07:34 +01:00
Torkel Ödegaard 10fd66b7c5 removed old sample directory, Closes #1333 2015-01-13 10:19:32 +01:00
Torkel Ödegaard 9f4ea7301a Merge branch 'master' of github.com:torkelo/grafana-private into pro 2015-01-12 18:59:48 +01:00
Torkel Ödegaard 14e1bc8702 Graph & Singlestat: New axis/unit format selector and more units (kbytes, Joule, Watt, eV), and new design for graph axis & grid tab and single stat options tab views, #1331 2015-01-12 17:05:43 +01:00
Torkel Ödegaard cc21c66b3a Updated design of singlestat options view, it now uses the new unit format selector, #1331 2015-01-12 17:02:58 +01:00
Torkel Ödegaard 1e9c51072a Added new units to dropdown in singlestat options, need to add new unit selector to singlestat panel, #1331 2015-01-12 16:24:44 +01:00
Torkel Ödegaard 4544f79471 Fixed influxdb and opentsdb editors to use latest markup and css, #1331 2015-01-12 16:18:35 +01:00
Torkel Ödegaard a3da11c5bc Fixed legend values check, got rid of the legend values checkbox, #1331 2015-01-12 15:59:22 +01:00
Torkel Ödegaard 66631da1c7 Some last polish to thew new axis editor view, #1331 2015-01-12 15:47:47 +01:00
Torkel Ödegaard d6f9ff34ce More work on new grap axis & grid edit tab, #1331 2015-01-12 15:37:22 +01:00
Torkel Ödegaard a1e39ce24d Fixed show/hide axis toggles, #1331 2015-01-12 14:31:15 +01:00
Torkel Ödegaard c39f9ed2f3 More work on refactoring and changing graph axis edit view, #1331 2015-01-12 14:00:30 +01:00
Torkel Ödegaard 3a27b610d5 Added all unit formats to new unit selector, #1331 2015-01-12 13:42:16 +01:00
Torkel Ödegaard 3dc30e4d8b Major reworking of the graph axis editor tab 2015-01-12 12:55:19 +01:00
Torkel Ödegaard 1a49d51d42 Merge branch 'Bps' of https://github.com/gitbisector/grafana into units
Conflicts:
	src/app/panels/graph/axisEditor.html
	src/app/panels/singlestat/editor.html
2015-01-12 10:06:12 +01:00
Torkel Ödegaard 0b46196ff5 Merge branch 'energy_formatting_units' of https://github.com/chron0/grafana into units
Conflicts:
	src/app/panels/graph/axisEditor.html
2015-01-12 10:04:11 +01:00
Torkel Ödegaard ea6cea6f29 Merge branch 'master' of https://github.com/grimpy/grafana into units 2015-01-12 10:02:10 +01:00
Torkel Ödegaard 93fb02509b Removed flicker when refreshing a singlestat panel with spark line 2015-01-12 09:27:20 +01:00
Ties Bos 09a0ef2013 Add Bps support, much like bps 2015-01-11 22:19:59 -08:00
Torkel Ödegaard ccbe055e5b Graph: Added right y axis label setting and graph support, Closes #599 2015-01-11 20:04:33 +01:00
Torkel Ödegaard 8e65f36131 Added system admin skeleton 2015-01-11 12:12:29 +01:00
Torkel Ödegaard b66894f727 Added system admin page skeleton 2015-01-11 12:12:12 +01:00
Torkel Ödegaard dc16c8c60c Added openldap docker/fig block, ldap auth seems to be complicated and take some time, not sure if that is high prio now 2015-01-11 12:11:49 +01:00
Torkel Ödegaard 92c3d80189 SingleStatPanel: You can now use template variables in pre & postfix, Closes #1321 2015-01-10 12:49:35 +01:00
Torkel Ödegaard 68cc3f86dd Handle default datasource management 2015-01-09 16:36:23 +01:00
Torkel Ödegaard 1d769fe41c Worked on handling marking a datasource as the default 2015-01-09 16:35:36 +01:00
Torkel Ödegaard 4edf0c9768 Fixed alert popup close icon 2015-01-09 16:28:58 +01:00
Torkel Ödegaard d562dcd90c Default datasource and event system test 2015-01-09 11:01:37 +01:00
Torkel Ödegaard f99e1ba441 Updated readme 2015-01-09 10:16:01 +01:00
Torkel Ödegaard 1b9b8ba2bf Fixed two icons 2015-01-09 10:15:42 +01:00
Torkel Ödegaard 16f7f68636 Plugins: fixed broken example panel plugin, Fixes #1318 2015-01-09 09:37:42 +01:00
Torkel Ödegaard c1272de462 Merge branch 'master' of github.com:torkelo/grafana-private into pro 2015-01-08 19:04:38 +01:00
Torkel Ödegaard 31cf8812f6 Fixed some icons that I had missed when upgrading font awesome to 4.2 2015-01-08 19:03:45 +01:00
Torkel Ödegaard a81a5315e1 Added graphite docker and fig config 2015-01-08 17:34:41 +01:00
Torkel Ödegaard f332f22bed Updated icons to font-awesome 4.2 2015-01-08 15:00:34 +01:00
Torkel Ödegaard 08ae183877 Merge branch 'master' of github.com:torkelo/grafana-private into pro 2015-01-08 14:38:10 +01:00
Torkel Ödegaard c4ce553936 Fixed icon in panel header when drilldown link is present 2015-01-08 14:37:50 +01:00
Torkel Ödegaard 6f987253ac Fixed dependency for unsavedChangesSrv 2015-01-08 12:27:54 +01:00
Torkel Ödegaard 1f1244f285 Upgrade Font-Awesome from 3.2 to 4.2 2015-01-08 12:23:11 +01:00
Torkel Ödegaard 3cf775a1f6 Added handling and view for 404 not found 2015-01-08 09:46:33 +01:00
Torkel Ödegaard 979349388c Added 404 not found view 2015-01-08 09:46:11 +01:00
Torkel Ödegaard cdcf88cbdf fixed path for solo panel ctrl 2015-01-08 09:34:46 +01:00
Torkel Ödegaard d573ee22f1 Fixed missing godep dependency for unit tests 2015-01-08 09:23:34 +01:00
Torkel Ödegaard 18ff1569b9 Added missing mysql driver package dependency 2015-01-08 09:01:39 +01:00
Torkel Ödegaard 3226a3a58e Fixed hashing of passwords, Closes #3 2015-01-08 09:00:00 +01:00
Torkel Ödegaard 164435f71d Moving code arround 2015-01-08 08:01:26 +01:00
Torkel Ödegaard 6d814af0cc Updated frontend 2015-01-08 07:40:17 +01:00
Torkel Ödegaard ffbbb5bc9e Graph: Fixed issue when using zero as a grid threshold, Fixes #1309 2015-01-07 20:03:51 +01:00
Torkel Ödegaard d3e11cabd5 Fixed link to server side rendered panel 2015-01-07 16:56:57 +01:00
Torkel Ödegaard 35326e1d92 Worked a little on anonymous access, needs more work 2015-01-07 16:37:24 +01:00
Torkel Ödegaard 9d629f2780 Tested with mysql, added simple fig file, need to write a more substantial fig setup 2015-01-07 14:05:57 +01:00
Torkel Ödegaard 1ae52d2472 Dashboard search by tag, and tag cloud now works, god dam I hate SQL 2015-01-07 12:37:24 +01:00
Torkel Ödegaard 37ba2511d5 fixed error handling in default dashboard route 2015-01-06 20:40:12 +01:00
Torkel Ödegaard bcdbec61d7 Dashboard search works better, tag cloud should be done soon 2015-01-06 18:39:26 +01:00
Torkel Ödegaard d03949a735 Worked on dashboard and tag search through backend 2015-01-06 18:38:48 +01:00
Torkel Ödegaard 63fa9f4535 InfluxDB: Fix handling of empty array in templating variable query, Fixes #1298 2015-01-06 17:32:39 +01:00
Torkel Ödegaard 3f266a3e1b Fixed dashboard search 2015-01-06 17:22:10 +01:00
Torkel Ödegaard a5c8bbfe1f Fixed query param in dashboard search for grafana datasource 2015-01-06 17:15:38 +01:00
Torkel Ödegaard 584f40d0c4 Merge branch 'master' of github.com:torkelo/grafana-private into pro 2015-01-06 14:16:06 +01:00
Torkel Ödegaard 72974719ac Remember last dashboard 2015-01-06 14:15:48 +01:00
Torkel Ödegaard d4c3463f68 InfluxDB: auto escape column names with special characters, Closes #1296 2015-01-06 13:26:09 +01:00
Torkel Ödegaard 47e4b77140 lodash remove override fix, Fixes #1304 2015-01-06 13:03:55 +01:00
Torkel Ödegaard 3e07605260 added a /api/metrics/test query that returns random walk series 2015-01-06 09:11:00 +01:00
Torkel Ödegaard 49b18e17d7 added test query feature to grafana datasource to generate a test graph on first login 2015-01-06 09:10:20 +01:00
Torkel Ödegaard 7c6f0ad445 Graphite: Added cumulative and minimumBelow graphite functions, Closes #1297 2015-01-05 20:25:55 +01:00
Torkel Ödegaard 0e3f91508e Added validation to stop duplicate dashboards with same name from being saved 2015-01-05 17:04:29 +01:00
Torkel Ödegaard 34427f34e8 side menu visible by default after login 2015-01-05 17:03:56 +01:00
Torkel Ödegaard 4131b75562 updated build script and readme with build instructions 2015-01-05 16:17:36 +01:00
Torkel Ödegaard 62af885c09 frontend build script fix 2015-01-05 11:05:21 +01:00
Torkel Ödegaard 344812f1e0 fix in grunt build script 2015-01-05 11:04:58 +01:00
Torkel Ödegaard d06174fb56 Added build script written in go that adds build symbols for version, commit and build date 2015-01-05 10:46:58 +01:00
Torkel Ödegaard 72d9532207 updated login view with build info 2015-01-05 10:46:16 +01:00
Torkel Ödegaard ab052a5ed3 Added missing letters to graphite datasource nested query interpolation, #1294 2015-01-05 10:02:03 +01:00
Torkel Ödegaard 6ebf766342 updated frontend 2015-01-05 08:47:44 +01:00
Torkel Ödegaard d1b31bb3d6 moved stuff around, trying to get rid of the pro folders and fixing some issues 2015-01-05 08:47:29 +01:00
Torkel Ödegaard 19c70a126f fixed oauth login redirect when using app sub url 2015-01-05 08:21:52 +01:00
Torkel Ödegaard 4cc8a9bd18 More work on getting grafana pro to work in sub url 2015-01-05 07:59:18 +01:00
Torkel Ödegaard 9be53f0a79 Updates to support sub url 2015-01-05 07:58:15 +01:00
Torkel Ödegaard f25a415a9e Work on making grafana work in sub url 2015-01-04 21:03:40 +01:00
Torkel Ödegaard 5feed2344a Fixes to make grafana work in sub url 2015-01-04 21:01:28 +01:00
Torkel Ödegaard 9ba34aabab updated build script to output bin in correct path 2015-01-04 18:57:16 +01:00
Torkel Ödegaard 0b855aeac9 updated wercker script 2015-01-04 18:37:45 +01:00
Torkel Ödegaard 176c042cb6 updated wercker script 2015-01-04 18:29:55 +01:00
Torkel Ödegaard db4ab8d8a7 updated wercker deploy step 2015-01-04 18:14:28 +01:00
Torkel Ödegaard 836f48b24c updated wercker file again 2015-01-04 18:08:43 +01:00
Torkel Ödegaard 9e01740193 updated wercker script 2015-01-04 18:05:16 +01:00
Torkel Ödegaard b77a2d0f42 updated wercker script 2015-01-04 17:58:04 +01:00
Torkel Ödegaard c83b0f2680 updated wercker deploy script 2015-01-04 17:52:10 +01:00
Torkel Ödegaard 5a3d3f5098 updated build and release tasks 2015-01-04 17:51:48 +01:00
Torkel Ödegaard 30c7cb80f9 corrected intendation in wercker file 2015-01-04 15:13:43 +01:00
Torkel Ödegaard a71e54113e fix to wercker file 2015-01-04 15:07:44 +01:00
Torkel Ödegaard dff19db58d added test deploy step to wercker file 2015-01-04 15:05:40 +01:00
Torkel Ödegaard 31520844b4 fix in wercker file 2015-01-04 14:44:22 +01:00
Torkel Ödegaard 282b3b3170 Updated wercker file 2015-01-04 14:37:24 +01:00
Torkel Ödegaard 55bc150701 added backend config frontend componenet 2015-01-04 14:36:41 +01:00
Torkel Ödegaard fc88429282 Graphite: query letter for the whole alfabet, Fixes #1294 2015-01-04 12:25:06 +01:00
Torkel Ödegaard ee443d91dd Fixed account creation on first github login 2015-01-01 22:27:19 +01:00
Torkel Ödegaard 318338f967 Updated zip release task 2015-01-01 22:26:57 +01:00
Torkel Ödegaard 29c9330965 Changes to config file loading 2015-01-01 15:29:10 +01:00
Torkel Ödegaard 7c72705bc5 more work on build process 2015-01-01 15:28:28 +01:00
Torkel Ödegaard ad4cf373a6 Work on unifying backend grafnaa and standalone grafana, and being able to build both from the same branch 2014-12-31 19:02:00 +01:00
Torkel Ödegaard 33e3fc70b2 trying to get revision asset replacements to work with different paths 2014-12-31 19:01:10 +01:00
Torkel Ödegaard 9c3cd87bee more work on two grunt build modes, backend/standalone modes 2014-12-31 16:19:45 +01:00
Torkel Ödegaard f8ddfec98c progress on being able to use exact same code base for backend and standalone version, building optimized build needs a little more work 2014-12-31 14:18:34 +01:00
Torkel Ödegaard 53ff171436 Standalone and backend mode with same code base is starting to work 2014-12-31 12:54:12 +01:00
Torkel Ödegaard 75c77a44c9 Merge branch 'master' of github.com:torkelo/grafana-private into pro
Conflicts:
	src/app/app.js
	src/app/controllers/all.js
	src/app/routes/all.js
	src/app/services/datasourceSrv.js
	src/test/test-main.js
2014-12-31 12:07:22 +01:00
Torkel Ödegaard 11b44f2b01 changes to make requirejs optimizer complete 2014-12-31 11:50:34 +01:00
Torkel Ödegaard d1e33ec071 Moved routes 2014-12-31 11:48:15 +01:00
Torkel Ödegaard 39f3cfc1eb moved dashboard stuff into a feature folder, not sure about this, but need better structure and better way to organize dependencies 2014-12-31 11:05:19 +01:00
Torkel Ödegaard 2f811375bb Moving templating to feature to feature folder 2014-12-31 09:35:00 +01:00
Torkel Ödegaard 8d7390e765 moved annotation editor and service to features folder 2014-12-31 09:28:30 +01:00
Torkel Ödegaard 8268c65c57 Trying to organize code more around features than the standard controllers, services structure 2014-12-30 21:24:55 +01:00
Torkel Ödegaard f9b0a01c74 restructuring 2014-12-30 21:08:56 +01:00
Torkel Ödegaard 0a4ccdcef2 moved all graphite code into features/graphite 2014-12-30 20:49:04 +01:00
Torkel Ödegaard 2b02c87b16 updated godeps file 2014-12-30 12:05:01 +01:00
Torkel Ödegaard e738a67e06 removed the need for symbolic link to grafana/src 2014-12-30 12:03:03 +01:00
Torkel Ödegaard b2f70c12b9 Updated packages 2014-12-30 11:38:36 +01:00
Torkel Ödegaard c4a48b35c0 Updated libs 2014-12-30 11:27:36 +01:00
Torkel Ödegaard 9cc47b6b58 Updated godeps file 2014-12-30 11:14:22 +01:00
Torkel Ödegaard 41083b08ce fixed typo in wercker.yml 2014-12-30 11:11:49 +01:00
Torkel Ödegaard 8d6b09e995 Trying to use godep in wercker build 2014-12-30 11:09:50 +01:00
Torkel Ödegaard 599a77c776 Added karma test step to build/relase grunt task 2014-12-30 11:05:24 +01:00
Torkel Ödegaard d7e796097a Another wercker update 2014-12-30 10:58:13 +01:00
Torkel Ödegaard 79e5dd3794 Updated wercker file 2014-12-30 10:48:46 +01:00
Torkel Ödegaard ef90f1dca6 Updated readme 2014-12-30 10:37:00 +01:00
Torkel Ödegaard aafe2c5b98 Updated session lib 2014-12-30 10:28:27 +01:00
Torkel Ödegaard e9fcca16bd updated to new golang/x/oauth2 2014-12-30 10:10:13 +01:00
Torkel Ödegaard c04a2ababd added basic wercker file 2014-12-30 09:33:27 +01:00
Torkel Ödegaard 8faa806c90 Refactoring dashboard delete and search 2014-12-29 13:58:06 +01:00
Torkel Ödegaard 164d11c816 InfluxDB now works in proxy mode, influxdb username and password is added in the backend and never exposed to frontend, #8 2014-12-29 13:36:08 +01:00
Torkel Ödegaard 27b11b1d79 InfluxDB now works in proxy mode, influxdb username and password is added in the backend and never exposed to frontend, #8 2014-12-29 13:35:51 +01:00
Torkel Ödegaard d9386cc2c3 Updated lastest.json 2014-12-29 11:24:14 +01:00
Torkel Ödegaard 38f348e642 Updated version to 1.9.1 2014-12-29 11:21:25 +01:00
Torkel Ödegaard 656ec9c48f SinglestatPanel: added more fontsize options, Closes #1187 2014-12-29 10:51:59 +01:00
Torkel Ödegaard ec98c201e4 Datasource options are now included in bootData 2014-12-28 19:30:14 +01:00
Torkel Ödegaard 47f226be3b login force full page reload 2014-12-28 19:29:41 +01:00
Torkel Ödegaard f3132b4513 Trying a different approach to providing frontend settings 2014-12-28 18:37:39 +01:00
Torkel Ödegaard cfabccc5f2 Work on handling frontend settings 2014-12-28 18:37:09 +01:00
Torkel Ödegaard f5e8f9334b Fixed test failing in chrome 2014-12-26 11:50:59 +01:00
Torkel Ödegaard 7be2105fd9 Merge pull request #1272 from matschaffer/plugin-spec-support
Allow for plugin testing.
2014-12-26 11:40:54 +01:00
Torkel Ödegaard 49ee388dcf Merge pull request #1274 from matschaffer/panel-alert-tweak
Bring panel alert to front and move tooltip to top.
2014-12-26 11:40:41 +01:00
Mat Schaffer 6cb4b4061c Bring panel alert to front and move tooltip to top.
This makes the panel a lot easier to mouse over without odd visual effects.
2014-12-23 18:12:04 -08:00
Mat Schaffer 85e50ece2e Allow for plugin testing.
With this change in place you can include plugin specs in `grunt test` by creating a `src/config.js` like that includes something like this:

```js
      plugins: {
        // list of plugin panels
        panels: [],
        // requirejs modules in plugins folder that should be loaded
        // for example custom datasources
        dependencies: ['grafana-plugins/atlasDatasource'],
        specs: ['grafana-plugins/specs/atlasDatasource-specs']
      }
```
2014-12-23 12:01:50 -08:00
Torkel Ödegaard 32ae0ea13e Plugins: Custom datasource example adapted to return timestamps in millieseconds, Fixes #1263 2014-12-23 11:32:25 +01:00
Torkel Ödegaard a55a606a55 Refactoring dashboard data access 2014-12-22 12:25:08 +01:00
Torkel Ödegaard 22bf20a135 Refactoring get account by id and by login to queries 2014-12-19 13:40:02 +01:00
Torkel Ödegaard 5dcf6ff2d3 Refactoring set using account 2014-12-19 13:12:47 +01:00
Torkel Ödegaard dede578c7d Updated to datasource management 2014-12-19 12:52:00 +01:00
Torkel Ödegaard 607b0c0c0e More refactoring and aligning code to the command query model 2014-12-19 11:53:27 +01:00
Torkel Ödegaard d5a59ac6b7 More migration to command/query and sql tests, looking good 2014-12-19 11:08:49 +01:00
Torkel Ödegaard ccba95542b Moved add collaborator to command way of doing it 2014-12-19 10:45:22 +01:00
Torkel Ödegaard 36c46112df Refactoring data access to command query model, and adding tests for sql code 2014-12-19 09:43:16 +01:00
Torkel Ödegaard 1b3bddd622 Graph: graph spinner should not stop until annotation request is complete, #1235 2014-12-19 08:39:23 +01:00
Torkel Ödegaard e5811e29b1 Added username, site and password for influxdb datasources 2014-12-19 08:19:12 +01:00
Torkel Ödegaard 525b367a6f Config.js generation, simple temp solution, first is default datasource 2014-12-19 07:42:20 +01:00
Torkel Ödegaard a7c816c65e Datasource proxy, switch to lookup by id 2014-12-19 07:27:25 +01:00
Torkel Ödegaard a2a0e0394d Work on data source proxying, #6 2014-12-18 20:26:06 +01:00
Torkel Ödegaard d69258e28f Backend can now generate config.js, the very basic stuff, more work needed 2014-12-18 15:41:38 +01:00
Torkel Ödegaard 4e542d8b83 Changed config.js to backend generated config.js 2014-12-18 15:41:01 +01:00
Torkel Ödegaard 731bb6ba03 Graph: hover tooltip and axis format units fix, bug introduced last week 2014-12-18 09:12:36 +01:00
Torkel Ödegaard fed06ef97d Graph: Fix for y axis and scaled units (GiB etc) caused rounding, for example 400 GiB instead of 378 GiB, Fixes #1251 2014-12-18 08:44:47 +01:00
Torkel Ödegaard ce947d4793 Update data sources now work, #8 2014-12-17 17:32:22 +01:00
Torkel Ödegaard ad91093902 Update datasource now works, #8 2014-12-17 17:31:57 +01:00
Torkel Ödegaard a58330f4d8 InfluxDB: Support more than 10 series name segments when using alias patterns, Closes #1126 2014-12-17 14:14:24 +01:00
Torkel Ödegaard 5a46c2397b OpenTSDB: Downsample query field now supports interval template variable, Closes #1242 2014-12-17 13:34:52 +01:00
Torkel Ödegaard adf4e72cf8 More general backend work, in the middle of the night... Zzzz 2014-12-17 03:09:54 +01:00
Torkel Ödegaard 0e97030716 More datasource admin, and backendSrv work 2014-12-17 03:09:25 +01:00
Torkel Ödegaard c7ed348ee8 Lots of progress on command/query bus concept, in memory sql testing, datasource admin 2014-12-16 21:05:49 +01:00
Torkel Ödegaard 27f07e9de2 Progress on data source admin 2014-12-16 16:45:07 +01:00
Torkel Ödegaard adb1502e72 Data source admin progress 2014-12-16 16:44:45 +01:00
Torkel Ödegaard f79588c191 Annotations: mini fix for annotations editor and the list of annotations 2014-12-16 15:47:10 +01:00
Torkel Ödegaard b70a3f0958 started work datasources admin 2014-12-16 12:04:08 +01:00
Torkel Ödegaard b3b096e204 Moved things around, started work on datasources view 2014-12-16 12:02:06 +01:00
Torkel Ödegaard f665a30d28 Added lint step to Makefile 2014-12-16 10:45:16 +01:00
Torkel Ödegaard 53b00d80d6 Fixed GetOtherAccountsFor query 2014-12-16 09:06:49 +01:00
Torkel Ödegaard 705455d5d6 Set active account now works again 2014-12-15 21:56:16 +01:00
Torkel Ödegaard 973b9cad36 moved all http route handling into single package named api 2014-12-15 21:25:02 +01:00
Torkel Ödegaard 1663cbbb34 Merge branch 'master' of github.com:torkelo/grafana-pro
Conflicts:
	.bra.toml
2014-12-15 20:50:38 +01:00
Torkel Ödegaard 26ec858903 Moved grafana bin file to bin folder 2014-12-15 17:36:42 +01:00
Torkel Ödegaard 9f766557f1 Dashboard: fixed minor issue when trying to aborting dashboard delete from search list, Fixes #1233 2014-12-15 17:30:01 +01:00
Torkel Ödegaard 6c351c724d merged grafana upstream 2014-12-15 16:18:24 +01:00
Torkel Ödegaard 373118c5ee Merge branch 'master' of github.com:torkelo/grafana-private into pro 2014-12-15 16:17:59 +01:00
Torkel Ödegaard a40c6e65dd Merge branch 'master' of github.com:torkelo/grafana-pro 2014-12-15 16:17:29 +01:00
Torkel Ödegaard 29749809d2 Updated 2014-12-15 16:17:20 +01:00
Torkel Ödegaard 846cf934f5 Graph: New legend option hideEmtpy to hide series with only null values from legend, Closes #1028 2014-12-15 16:13:34 +01:00
Torkel Ödegaard 162eb4ca35 Panel: duplicate now always adds the duplicated panel on the same row instead of creating a new row, Closes #1185 2014-12-15 15:21:29 +01:00
Torkel Ödegaard 2f18444a43 Graphite: alt node suggestions will now not include wildcard or template variables if the node is empty, Closes #1230 2014-12-15 15:16:03 +01:00
Torkel Ödegaard 8a4ff5bddc Merge pull request #1205 from noise/master
fix #1204: add separate datasource parameter withCredentials
2014-12-15 15:05:43 +01:00
Torkel Ödegaard 4ae7648bea Merge pull request #1193 from jklukas/master
Add offsetToZero to gfunc.
2014-12-15 12:37:27 +01:00
Torkel Ödegaard dc75559758 Merge pull request #1206 from nikut/sumSeriesWithWildcards-params
allow multiple params for sumSeriesWithWildcards
2014-12-15 12:35:19 +01:00
Torkel Ödegaard acd944a649 Graphite: movingAverage / movingMedian parameter type impovement, now handles int and interval parameter, Fixes #1207 2014-12-15 12:33:04 +01:00
Torkel Ödegaard 3c1b30e3c1 Graphite: parser fix for hard case where ip numbers are used as segments, Fixes #1224 2014-12-15 11:36:08 +01:00
Torkel Ödegaard 5daefc8b8e White theme: css fix for links in annotations tooltips, Fixes #1216 2014-12-15 10:53:37 +01:00
Torkel Ödegaard 715b9cbad0 Small fix, removed test code change 2014-12-11 09:44:45 -08:00
Torkel Ödegaard 7194e91d3b Singlestat: small fix for threshold evalutations, use greater or equal for threshold checks, Fixes #1192 2014-12-11 09:43:36 -08:00
Torkel Ödegaard 69d56b8ed7 Graph: fix for series tooltip when one series is hidden/disabled, #1199 2014-12-11 07:47:04 -08:00
Torkel Ödegaard 982a5b1a39 Panel: drilldown link, moved new params tooltip to params property, #1210 2014-12-11 07:26:00 -08:00
Torkel Ödegaard 6b8cb4ac7f Merge pull request #1210 from falkenbt/patch-1
Extend tooltip for drilldown link
2014-12-11 07:23:07 -08:00
Torkel Ödegaard 397f253180 Merge pull request #1214 from DazWorrall/useseriesabove
Added graphite function 'useSeriesAbove'
2014-12-11 07:21:00 -08:00
Darren Worrall f93b6f7d85 Add missing default parameters to useSeriesAbove 2014-12-10 15:57:12 +00:00
Darren Worrall edb2cf2cf2 Added graphite function 'useSeriesAbove' 2014-12-10 15:51:45 +00:00
falkenbt fb12dd4688 Extend tooltip for drilldown link
It took me a bit to find out that variables need to be passed with var-variableName. 
The behavior is not specific to the drilldown link but I couldn't find it in any documentation elsewhere and it might help others to have it documented right here.
2014-12-09 17:23:44 +01:00
Niku Toivola 0a561e5aeb allow multiple params for sumSeriesWithWildcards 2014-12-09 11:41:01 +02:00
bret barker c140c8cac9 fix #1204: add separate datasource parameter withCredentials 2014-12-08 09:35:55 -05:00
Jeff Klukas 06ab063671 Add offsetToZero to gfunc. 2014-12-04 09:00:47 -08:00
Torkel Ödegaard 1591a486cc Fixed typo in graphite editor tooltip 2014-12-02 14:51:21 -08:00
Torkel Ödegaard b53efed1ef formating fix for the changelog 2014-12-02 14:49:42 -08:00
Torkel Ödegaard 7a202db5ad Updated latest.json 2014-12-02 14:24:34 -08:00
Torkel Ödegaard af1ae7cab4 SinglestatPanel: decimal precision fix for uneven numbers, #1066 2014-12-02 13:55:56 -08:00
Torkel Ödegaard 24519cbf78 Updated version to 1.9.0 2014-12-02 11:21:18 -08:00
Torkel Ödegaard 01305462aa Row: css fix for collapsed rows with empty title 2014-12-02 11:07:51 -08:00
Torkel Ödegaard 90ae59ccaf Fixed png rending 2014-12-01 13:25:57 -08:00
Torkel Ödegaard ff6a2d014a Refactored command/query interface 2014-12-01 08:56:03 -08:00
Torkel Ödegaard 246f41b88a Bus experiment 2014-11-28 22:16:49 +01:00
Torkel Ödegaard a799fac983 Added Godeps and embedded dependencies 2014-11-28 11:56:03 +01:00
Torkel Ödegaard 50164324f3 moved back to a main.go file in root 2014-11-28 11:51:34 +01:00
Torkel Ödegaard 634d8c9978 sync with oss master 2014-11-28 09:14:54 +01:00
Torkel Ödegaard e5eb0e3874 Merge branch 'master' of github.com:torkelo/grafana-private into pro 2014-11-27 15:01:48 +01:00
Torkel Ödegaard 63273f4ccf Merge branch 'master' of github.com:torkelo/grafana-private into pro 2014-11-27 14:59:58 +01:00
Torkel Ödegaard 7a4077405e Annotations: added html sanitation to prevent markup injection/XSS, Closes #1121 2014-11-27 14:46:01 +01:00
Torkel Ödegaard 9594effb6c Graph: Fix for tooltip series order when series draw order was changed with zindex property, Fixes #1108 2014-11-27 14:30:17 +01:00
Torkel Ödegaard e750498696 Templating: added validation for allowed characters in variable names, Closes #1106 2014-11-27 14:22:55 +01:00
Torkel Ödegaard 69e18905f5 Templating: added validation to template editor for variable names, Fixes #1133 2014-11-27 14:17:31 +01:00
Torkel Ödegaard ac4524cf9b Firefox: Workaround for Firefox bug, casued input text fields to not be selectable and not have placeable cursor, Fixes #1123 2014-11-27 13:35:03 +01:00
Torkel Ödegaard 7baad7ff10 Tech: updated angular libs 2014-11-27 13:31:55 +01:00
Torkel Ödegaard d7ef6daeb8 SinglestatPanel: Fixed absolute drilldown link issue, Fixes #1150 2014-11-27 10:42:05 +01:00
Torkel Ödegaard d8d5516e24 Create README.md 2014-11-26 12:57:16 +01:00
Torkel Ödegaard 93e3908a63 Merge pull request #1140 from johnou/support_negative_thresholds
Support for threshold colouring of zero / negative values.
2014-11-26 12:33:29 +01:00
Torkel Ödegaard d7ee4b4573 Added missing files 2014-11-26 09:58:36 +01:00
Torkel Ödegaard bf5f6ce97c Merge branch 'master' of github.com:grafana/grafana
Conflicts:
	CHANGELOG.md
2014-11-26 09:36:11 +01:00
Torkel Ödegaard ed2ca5fced Graph: Fix to legend value Max and negative values, Fixes #1136 2014-11-26 09:34:21 +01:00
Johno Crawford 8dfe85f23e Support for negative thresholds. 2014-11-25 19:31:35 +00:00
Torkel Ödegaard a492eceff0 refactoring 2014-11-24 17:37:20 +01:00
Torkel Ödegaard be781bdb98 Tried postgres 2014-11-24 10:17:13 +01:00
Torkel Ödegaard a8f915f049 Merge branch 'macaron' 2014-11-24 08:20:22 +01:00
Torkel Ödegaard e750080f00 removed old code 2014-11-24 08:20:16 +01:00
Torkel Ödegaard 79beefe57c Updated grafana datasource 2014-11-24 08:05:07 +01:00
Torkel Ödegaard fcdcd63dc7 get other accounts works 2014-11-24 08:04:41 +01:00
Torkel Ödegaard 846992930c Merge pull request #1109 from jklukas/master
Purge timspan variable from scripted templates.
2014-11-23 15:43:46 +01:00
Torkel Ödegaard a1d652d578 SinglestatPanel: Added null point handling, and value to text mapping, Closes #1130, Fixes #1120, #951 2014-11-22 15:26:23 +01:00
Torkel Ödegaard d0e057722b Graphite: added more optional parameters to aliasByNode function definition, #1124 2014-11-21 17:48:49 +01:00
Torkel Ödegaard f04932aa67 More work on collaborators, and sql store 2014-11-21 16:43:04 +01:00
Torkel Ödegaard d1be4e2a90 Light theme: tweaks to background color and table highlight rows, Closes #1119 2014-11-21 10:44:37 +01:00
Torkel Ödegaard 3cde783d1d Graphite: Lexer fix, allow equal sign (=) in metric paths, Fixes #1114 2014-11-21 10:25:36 +01:00
Torkel Ödegaard e933369f56 Singlestat: changed singlestat title to centered position, Closes #1117 2014-11-21 10:17:08 +01:00
Torkel Ödegaard e109f8d69c Graphite: added timeStack function definition, Closes #1118 2014-11-21 10:11:21 +01:00
Torkel Ödegaard d198057eaf InfluxDB: fixed issue with using custom/absolute from time to now(), Fixes #1113 2014-11-21 10:06:42 +01:00
Torkel Ödegaard 88c2f18b20 Graphite: fixed aliasSub graphite func definition 2014-11-21 08:24:13 +01:00
Jeff Klukas a30a604228 Purge timspan variable from scripted templates. 2014-11-20 09:32:19 -06:00
Torkel Ödegaard 4eefa73441 Progress on account and dashboard save/load 2014-11-20 15:19:44 +01:00
Torkel Ödegaard 00b4d233cc Small progress, save search works 2014-11-20 13:02:25 +01:00
Torkel Ödegaard eb2c078898 Progres on move to sql from rethinkdb 2014-11-20 12:11:07 +01:00
Torkel Ödegaard 4b5eadf7b5 Merge branch 'master' of github.com:torkelo/grafana-private into pro 2014-11-20 09:16:49 +01:00
Torkel Ödegaard 9b68911d00 Made it it compile again 2014-11-20 09:16:28 +01:00
Torkel Ödegaard 62b58d8bb0 Updated scripted dashboard example 2014-11-20 09:09:59 +01:00
Torkel Ödegaard a5951592f7 Merge branch 'master' of github.com:jklukas/grafana into jklukas-master 2014-11-20 09:08:00 +01:00
Torkel Ödegaard 6392d6514e Graphite: fixed: moving graphite function left/right did not update query, Fixes #1100 2014-11-20 09:00:30 +01:00
Torkel Ödegaard 91d6641326 Graph: fixed issue with shared tooltip when one or more series is hidden, Fixes #1094 2014-11-19 16:01:04 +01:00
Torkel Ödegaard dd398f73c2 Css tweak to dark theme, mark submenu carets (ie arrows) white, Fixes #1098 2014-11-19 15:41:16 +01:00
Torkel Ödegaard de10bd4ef6 DrilldownLink: template variables in params property was not interpolated, Fixes #1095 2014-11-19 15:33:00 +01:00
Torkel Ödegaard 56321da9c1 Bug: Fixed position for drilldown link tooltip when dashboard requires scrolling, Fixes #1093 2014-11-19 15:26:14 +01:00
Torkel Ödegaard d0d1c5ea5f updated changelog 2014-11-19 11:57:00 +01:00
Torkel Ödegaard e16872c864 Updated angular-dragdrop to fix issue with IE9, Fixes #1087 2014-11-19 11:55:18 +01:00
Jeff Klukas 1e425244d2 Refactor timspan default in scripted examples. 2014-11-18 14:24:55 -06:00
Torkel Ödegaard 0c6618d2f6 Small tweaks 2014-11-18 16:58:26 +01:00
Torkel Ödegaard a9d7823186 Singlestat: only show thresholds options when coloring option is checked, Fixes #1077 2014-11-18 08:27:43 +01:00
Torkel Ödegaard 6c0f5329aa Graph: improved decimal precision in legend and graph hover when graph ticks use single decimal point, now graph legend and tooltip always use one more decimal precision than axis ticks (if axis ticks has decimals), Fixes #1072 2014-11-17 19:20:27 +01:00
Torkel Ödegaard a677a4feff Updated changelog with share panel feature, #864, it was missing 2014-11-17 16:13:47 +01:00
Torkel Ödegaard 4f674c8d19 Updated changelog with RC1 release date 2014-11-17 15:21:43 +01:00
Torkel Ödegaard e197163019 Changed version to 1.9-rc1 2014-11-17 11:19:17 +01:00
Torkel Ödegaard 7ba0099fa9 Panel: fixes to panel menu when in edito/fullscreen mode, Fixes #1069 2014-11-17 09:59:37 +01:00
Torkel Ödegaard f45797ec4b trying out panel actions buttons 2014-11-17 09:11:54 +01:00
Torkel Ödegaard 02e1ac12b2 InfluxDB: fixed annotations, broken after switch to millisecond resolution, Fixes #1061 2014-11-17 07:09:21 +01:00
Torkel Ödegaard bde138177d Singlestat: fixed decimal issue when value was 1, Fixes #1066 2014-11-15 10:38:01 +01:00
Torkel Ödegaard 2423f470ef Merge with oss master 2014-11-14 17:56:27 +01:00
Torkel Ödegaard e5219af481 Merge branch 'master' of github.com:torkelo/grafana-private into pro
Conflicts:
	src/app/components/require.config.js
2014-11-14 17:40:16 +01:00
Torkel Ödegaard 21a4f3cc05 updated 2014-11-14 17:16:40 +01:00
Torkel Ödegaard a9d9939bdd Merge branch 'pro' of github.com:torkelo/grafana-private into pro 2014-11-14 17:15:44 +01:00
Torkel Ödegaard 41fb2b4cc4 updated 2014-11-14 17:14:34 +01:00
Torkel Ödegaard 526e232c09 Merge branch 'pro' of github.com:torkelo/grafana-private into pro 2014-11-14 17:14:02 +01:00
Torkel Ödegaard f2ed3d3ff2 Updated 2014-11-14 17:13:44 +01:00
Torkel Ödegaard f5b1b192a0 Macaron and sqllite 2014-11-14 17:13:33 +01:00
chrono a77c33d9b7 Added J,W & eV to unit formatting 2014-11-14 14:19:55 +01:00
Torkel Ödegaard 640c558446 Graph: added export graph time series data as csv file feature, accessed from panel menu dropdown, #861 2014-11-14 12:12:09 +01:00
Torkel Ödegaard c11ce99bb3 SinglestatPanel: another small fix for drilldown link click while fullscreen mode, #1041 2014-11-14 11:41:36 +01:00
Torkel Ödegaard 8ad83b8d58 SinglestatPanel: small fix for drilldown link click while fullscreen mode, #1041 2014-11-14 11:36:20 +01:00
Torkel Ödegaard 873d3d7c4a SinglestatPanel: Added integration with drilldown link feature, if a drilldown link is present the entire singlestat panel will act as a link, with hover tooltip containing the link name, #951, #1041 2014-11-14 09:33:03 +01:00
Torkel Ödegaard d4adaaaf2b Graphite: annotations stop working after recent change to millisecond resolution, Fixes #1061 2014-11-14 08:19:49 +01:00
Torkel Ödegaard 104493e725 SharePanel: fix for interval variables and auto value, Fixes #1056 2014-11-13 15:23:34 +01:00
Torkel Ödegaard b172e7afdc SinglestatPanel: mini fix 2014-11-13 15:23:34 +01:00
Torkel Ödegaard b1efbeb220 Merge pull request #1057 from kaos/master
panelmeta: fix metrics tab title.
2014-11-13 15:06:31 +01:00
Andreas Stenius f2a6657b72 panelmeta: fix metrics tab title. 2014-11-13 14:29:42 +01:00
Torkel Ödegaard 4eb4974909 Graph: change to current legend value handling, if last value is null, current will pick next to last value, Closes #190 2014-11-12 13:47:06 +01:00
Torkel Ödegaard 3e2c898881 Graph: legend fix for current legend value 2014-11-12 13:40:22 +01:00
Torkel Ödegaard 2fb176a244 Graphite: fix for graphite query editor, when adding functions and the function selection dropdown get push down the selection did not work properly, Fixes #1038 2014-11-12 12:57:22 +01:00
Torkel Ödegaard 53a6a7b305 Annotations: fixed spelling issue in annoation error handling code 2014-11-12 12:41:53 +01:00
Torkel Ödegaard 2045380223 Graph: moved graph and graph tooltip directive into panels/graph folder 2014-11-12 11:58:04 +01:00
Torkel Ödegaard 1c0fc3c924 Graph: small fix to legend sorting, #1030 2014-11-12 10:19:33 +01:00
Torkel Ödegaard 0b966b7a28 Graph: legend sorting while in table mode implemented, panel persisted property, Closes #1030 2014-11-12 10:11:01 +01:00
Torkel Ödegaard 882a477c0f Graph: increase decimal precision in tooltip and legend values when unit scaling is applied, #1043 2014-11-12 09:09:05 +01:00
Torkel Ödegaard ebcf2c3f68 InfluxDB: Support for sub second resolution graphs, Closes #714, #728, #752 2014-11-12 08:39:04 +01:00
Torkel Ödegaard 54fafb3a76 Graph: fix to typeahead for series overrides, (bug introduced in recent commit), Fixes #1046 2014-11-11 21:09:50 +01:00
Torkel Ödegaard 01bd662046 Graph: legend fix for bug when running optimized build, Fixes #1045 2014-11-11 21:06:01 +01:00
Jo De Boeck 98d934c8a4 Add mbytes and kbytes y_formats
Usefull when input data is stored in kbytes or mbytes
2014-11-11 19:22:22 +02:00
Torkel Ödegaard 9e26d3e85d SinglestatPanel: increased scaled decimal offset by 2 for, improves decimal precision when units gets scaled, #1043, #951 2014-11-11 16:30:20 +01:00
Torkel Ödegaard 17114778b7 Panel: fixed bug introduced in recent commit that caused blank title in optimized build, #1041 2014-11-11 15:00:31 +01:00
Torkel Ödegaard 98037ca0c6 SinglestatPanel: fixed issue when value is zero, #1039 2014-11-11 14:55:23 +01:00
Torkel Ödegaard 4ec59e8211 Tech: cleanup unused angular-strap directives, Closes #1029 2014-11-11 13:57:39 +01:00
Torkel Ödegaard 1e6a5ff8ec Updated changelog with #1041 2014-11-11 13:44:25 +01:00
Torkel Ödegaard c12d830162 Panel: added search typeahead for dashboard links, #1041 2014-11-11 13:38:34 +01:00
Torkel Ödegaard a49a9b3b64 Panel: more work on panel links, #1041 2014-11-11 12:51:57 +01:00
Torkel Ödegaard 5da3da5962 PanelLinks: began work on drilldown panel links feature, #1041 2014-11-11 11:48:27 +01:00
Torkel Ödegaard 8bb51d47f8 DashboardViewState: small fix for bug that caused issue for singlestat panel 2014-11-11 08:07:05 +01:00
Torkel Ödegaard 381b9ee7ee Graph: legend css tweaks 2014-11-11 07:51:52 +01:00
Torkel Ödegaard a301c96c9d SharePanel: fix to share feature, Closes #1035 2014-11-10 16:18:59 +01:00
Torkel Ödegaard f9c3cdab67 UI: replaced native confirm dialogs with nicer custom ones 2014-11-10 15:01:30 +01:00
Torkel Ödegaard 27ec0d532e began work on custom confirm modal 2014-11-10 10:40:45 +01:00
Torkel Ödegaard 3aa619b617 Panel: fixed duplicate panel, broken after yesterdays panel menu model refactoring 2014-11-09 09:44:16 +01:00
Torkel Ödegaard ef92272bee Panel: fixed menu position after moving json to new dropdown 2014-11-08 19:28:16 +01:00
Torkel Ödegaard 815ef05daf Graph: refactoring some stuff with legend values 2014-11-08 19:16:22 +01:00
Torkel Ödegaard 2ab19148c1 Graph: toggle legend on/off from panel menu, #941, no keyboard shortcut yet, but could come 2014-11-08 18:12:05 +01:00
Torkel Ödegaard d12f4a4aee Panels: refactoring panel meta model & menu, will open up panel specific menu actions 2014-11-08 16:27:49 +01:00
Torkel Ödegaard 81b1939f92 Graph: small fix to legend table mode 2014-11-08 11:44:23 +01:00
Torkel Ödegaard 834daeecd0 OpenTSDB: updated change log with templating support PR #917 2014-11-08 11:35:42 +01:00
Torkel Ödegaard 6aa0208316 Updated changelog with #1030 2014-11-07 14:23:18 +01:00
Torkel Ödegaard aa87d8eb22 Graph: changed style of legend table mode to full width table and with headers, #1030 2014-11-07 14:20:58 +01:00
Torkel Ödegaard cd21fa7016 Graph: new legend table display style, #1030 2014-11-07 13:57:52 +01:00
Torkel Ödegaard 7ba4f6b93f Singlestat: removed debug console.log 2014-11-07 13:56:28 +01:00
Torkel Ödegaard e16a51ad06 Singlestat: fixed decimal precision bug, #951 2014-11-07 13:54:19 +01:00
Torkel Ödegaard 6861dc137f Graph: fix for legend show/hide toggle, broken by recent legend rewrite 2014-11-07 13:39:47 +01:00
Torkel Ödegaard e530e4d4bc Tech: rewrite of how the legend is implement, performance increase, and will make future legend enhancements easier 2014-11-07 12:15:34 +01:00
Torkel Ödegaard d150bc1e52 Graphite: added mapSeries and reduceSeries functions, Closes #1018 2014-11-07 06:34:47 +01:00
Torkel Ödegaard cc8961360a Graphite: added maxDataPoints override option and a help section that describes how graphite point consolidation work, Closes #5 2014-11-06 16:25:46 +01:00
Torkel Ödegaard c0539e483e Added singlestat panel to changelog 2014-11-06 14:10:25 +01:00
Torkel Ödegaard f0b7099be3 SingleStatPanel: renamed panel to singlestat 2014-11-06 14:07:32 +01:00
Torkel Ödegaard ee183d4574 SingleStatPanel: some more tweaks and polish 2014-11-06 13:57:16 +01:00
Torkel Ödegaard fa813024ca SingleStatPanel: various fixes 2014-11-06 12:30:42 +01:00
Torkel Ödegaard 37176fa42d SingleStatPanel: Finnaly solved automatic decimal precision calculation for singlestat panel, #951 2014-11-06 12:17:46 +01:00
Torkel Ödegaard 7ff8931def SingleStatPanel: added font size options for value, prefix and postfix, #951 2014-11-06 10:47:46 +01:00
Torkel Ödegaard 2a962bf8fd Singlestat: progress on singlestat panel 2014-11-06 09:56:50 +01:00
Torkel Ödegaard bbbcba8b98 Merge branch 'master' into valuepanel 2014-11-06 08:54:01 +01:00
Torkel Ödegaard ecdcd10612 Graph: Series hide/show toggle changed to be default exclusive, so clicking on a series name will show only that series. (SHIFT or meta)+click will toggle hide/show. Closes #1007 2014-11-03 10:21:16 +01:00
Torkel Ödegaard 10ea140358 MetricEditors: Ability to reorder metric queries in metrics tag, Closes #716, Closes #856 2014-11-03 08:56:13 +01:00
Torkel Ödegaard 74e0309241 Fixes #912, URL parameters are lost when switching to fullscreen/edit mode 2014-11-02 11:56:51 +01:00
Torkel Ödegaard c88bfbbf82 SingleStatPanel: editor cleanup 2014-11-02 11:36:11 +01:00
Torkel Ödegaard 4edb89eeb9 Merge branch 'summarize-alignToFrom' of github.com:adriensamson/grafana into adriensamson-summarize-alignToFrom 2014-10-30 09:24:27 +01:00
Torkel Ödegaard cdb4b3cc7d Panel: fix fullscreen/edit view and page refresh when panel is in collapsed/hidden row, Fixes #992 2014-10-29 09:51:44 +01:00
Torkel Ödegaard ed8dd03fa1 Merge branch 'master' into valuepanel 2014-10-29 09:24:49 +01:00
Torkel Ödegaard e5bb7f7c2d CustomDatasource: fixed build optimization issue with custom datasources, Fixes #994 2014-10-28 19:00:46 +01:00
Torkel Ödegaard a7b0f6dc9f ScriptedDashboard: datasource services are now available in scripted dashboards, you can query datasource for metric keys, generate dashboards, and even save them in a scripted dashboard (see scripted_gen_and_save.js for example), Closes #991 2014-10-28 16:16:52 +01:00
Torkel Ödegaard c42986c07d Merge branch 'services_in_scripted_dashboard' 2014-10-28 16:14:41 +01:00
Torkel Ödegaard a982dd1765 ScriptedDashboard: using grafana services in scripted dashboard 2014-10-28 16:14:28 +01:00
Torkel Ödegaard c3900398fc Merge pull request #917 from mchataigner/master
adding templating in opentsdb graphs
2014-10-28 15:39:14 +01:00
Torkel Ödegaard 4b79a5e9da OpenTSDB: merged PR #930 adding counter max and counter reset options to OpenTSDB query editor 2014-10-28 12:41:17 +01:00
Torkel Ödegaard eed2feea97 Merge branch 'opentsdb-counterMax' of github.com:rsimiciuc/grafana into rsimiciuc-opentsdb-counterMax 2014-10-28 12:36:15 +01:00
Torkel Ödegaard 60a2d9f624 Dashboard: fix for collapsed rows became invisible when hide controls was enabled, Fixes #987 2014-10-27 10:42:13 +01:00
Torkel Ödegaard 3cd33b6ffc Tech: added cache busting for requirejs fetches, will only affect config.js for optimized builds, should solve user issues where browsers cache config.js which causes initial confusing setup/config issues. 2014-10-26 08:45:42 +01:00
Torkel Ödegaard e3942b3438 Graph: multi series tooltip fix for long series names, and metric value row alignment, Fixes #983, #984 2014-10-25 12:01:31 +02:00
Torkel Ödegaard 0bf37b8c00 Merge pull request #981 from swoop-inc/mb_value_update_bug
When get_data fails the legend should be cleared
2014-10-24 21:10:07 +02:00
Torkel Ödegaard 0e5dbf3889 Merge pull request #980 from maxstepanov/patch-1
Replace all $interval occurrences in query string
2014-10-24 21:07:38 +02:00
Torkel Ödegaard 785f96aabe Merge pull request #975 from swoop-inc/mb_support_influx_regex_merge
Add support for regex based series merging in InfluxDB
2014-10-24 21:01:52 +02:00
Mark Bell 5cec936128 When get_data fails the legend should be cleared 2014-10-24 14:51:45 -04:00
Max Stepanov 02861142cb Replace all $interval occurrences in query string
because template
2014-10-24 20:06:39 +03:00
Torkel Ödegaard 1cfc4d2f31 ScriptedDashboard: bugfix for scripted dashboards and collapsed rows, Fixes #965 2014-10-24 11:48:08 +02:00
Mark Bell 08e816a539 Add support for regex based series merging in InfluxDB 2014-10-23 10:35:57 -04:00
Torkel Ödegaard 272cf64aac SinglsStatPanel: more work on sparkline option for single stat panel 2014-10-23 13:44:15 +02:00
Torkel Ödegaard ed57a4099b Merge branch 'master' into valuepanel 2014-10-23 13:35:54 +02:00
Torkel Ödegaard 79c5d48a3c Timepicker: Fix for Zoom out top menu link still visible when timepicker was disabled, Fixes #963 2014-10-23 12:36:25 +02:00
Adrien Samson 6c70122e55 Add alignToFrom param to summarize 2014-10-22 11:31:41 +02:00
Torkel Ödegaard 6c83699e6f Panel: css fix for panel fullscreen/edit mode for latest chrome 38 and Firefox 33, #954 2014-10-20 12:14:58 -04:00
Torkel Ödegaard ff254ce08d Help: fix for help modal shortcut, and added missing shortcut, #952 2014-10-20 11:33:07 -04:00
Torkel Ödegaard 8a80ea26b8 Graph: fix for series override controller unit test 2014-10-20 10:10:18 -04:00
Torkel Ödegaard b85fe62389 Graph: series override dropdown menu select did not work after recent commit that added typeahead to this dropdown 2014-10-20 10:06:34 -04:00
Torkel Ödegaard 31a4d9204c SingleStatPanel: Added graph to single stat panel, #951 2014-10-19 19:36:59 -04:00
Torkel Ödegaard 69fdfd5cb3 Merge branch 'master' into valuepanel 2014-10-19 12:34:01 -04:00
Torkel Ödegaard 16e7980982 Help: added help modal, accessed by shortcut '?', the help modal only contains a list of all shortcuts right now but will be extended in the future, #952 2014-10-19 12:30:41 -04:00
Torkel Ödegaard e3e08cf8e7 Graph: fix for second y axis tick unit labels wrapping on the next line, Fixes #505 2014-10-18 13:31:15 -04:00
Torkel Ödegaard cae6626b06 StatsPanel: small change to stats panel, #951 2014-10-17 10:38:15 -04:00
Torkel Ödegaard 956d93e871 StatsPanel: fine tuning colors 2014-10-17 10:36:04 -04:00
Torkel Ödegaard 7c4d1b7b01 StatsPanel: made big values template based 2014-10-16 13:44:52 -04:00
Torkel Ödegaard 9866e0851b Graph: single series tooltip and unit format change fix, Closes #946 2014-10-16 11:21:56 -04:00
Torkel Ödegaard 0a97a2435b StatsPanel: more progress 2014-10-16 11:16:20 -04:00
Torkel Ödegaard 5c80f03eae StatsPanel: small progress on stats panel 2014-10-15 18:16:04 -04:00
Torkel Ödegaard dd03a4b011 Dashboard: fix for fullscreen mode and small gap sometimes showing the underlying dashboard 2014-10-15 17:37:08 -04:00
Torkel Ödegaard 6cd1bc32fe StatsPanel: more work on stats panel 2014-10-15 17:29:47 -04:00
Torkel Ödegaard dd0193a9a8 Dashboard: removed wip stats panel from settings.js, accidental commit 2014-10-15 15:48:57 -04:00
Torkel Ödegaard 123faa6f8e Merge branch 'master' into valuepanel 2014-10-15 15:48:09 -04:00
Torkel Ödegaard f743288ce0 Graph: fix for legends on the side and graph dimensions adaptation 2014-10-15 15:47:59 -04:00
Torkel Ödegaard a1d764bd26 Merge branch 'master' into valuepanel
Conflicts:
	src/app/components/settings.js
2014-10-15 15:41:58 -04:00
Torkel Ödegaard 61f6bd2c80 Graph: added typehead dropdown menu combination for the series override selection 2014-10-15 14:13:53 -04:00
Torkel Ödegaard fe620d8e44 Graph: fill below to series override option not automatically adds lines=false, as overrides, if you want lines just for the fill below series just remove the overrides, #940 2014-10-15 11:07:51 -04:00
Torkel Ödegaard 22db28d3e7 Graph: New series style override option 'Fill below to', useful to visualize max & min as shadow for the mean, #940 2014-10-15 10:55:46 -04:00
Torkel Ödegaard 1330488e13 Panel: plugins panels can now reside outsude the app/panels directory, added example plugin panel 2014-10-14 16:57:33 -04:00
Torkel Ödegaard 22297be3cf Merge branch 'master' into valuepanel 2014-10-13 12:30:34 -04:00
Raul Simiciuc 29d7d6994a added reset value 2014-10-13 13:43:25 +01:00
Raul Simiciuc 87e8162a2d fixed identation 2014-10-13 13:07:46 +01:00
Raul Simiciuc 38b71bf386 adding counterMax option to opentsdb 2014-10-13 12:36:26 +01:00
Torkel Ödegaard 920689b80e InfluxDB: series lookup & typeahead is now handled by influxdb list series regex queries, Closes #888 2014-10-12 12:47:50 -04:00
Torkel Ödegaard e9c7523646 Graphite: added second optional parameter to averageSeriesWithWildcards, Closes #926 2014-10-11 06:24:44 -04:00
Torkel Ödegaard 88bbc720ca Graph: removed console logs 2014-10-11 06:08:33 -04:00
Torkel Ödegaard 1bf1469c80 Merge branch 'master' of github.com:grafana/grafana 2014-10-10 13:37:43 -04:00
Torkel Ödegaard c74eda20dc Graph: fix for legend values min & max, avg & current when series only has null values, Closes #923 2014-10-10 13:37:33 -04:00
Torkel Ödegaard db0a5bd537 Graph: fix for legend values min & max, avg & current when series only has null values, Closes #923 2014-10-10 10:45:34 -04:00
Torkel Ödegaard c6cb01aa3b Graph: series tooltip length check 2014-10-10 10:05:12 -04:00
Torkel Ödegaard 7463f91878 Tech: removed jquery-ui and jquiery-ui-angular-drag-drop, replaced with native drag drop for angular, saved ~50kb in js libs, much cleaner & simpler usage, Also changed some behavior for drag drop, now panels are replaced when droped on (switch places), Closes #920 2014-10-09 20:30:23 -04:00
Mathieu Chataigner 482b31298f fixing unused var 2014-10-09 19:12:56 +02:00
Torkel Ödegaard ce46ca2f39 Search: fixed bug introduced in recent PR #900, related to comment on #909 2014-10-09 12:31:51 -04:00
Mathieu Chataigner de00d18a7e adding templating in opentsdb graphs 2014-10-09 16:59:29 +02:00
Torkel Ödegaard b0cf0c558d Merge branch 'master' into valuepanel 2014-10-09 09:55:49 -04:00
Torkel Ödegaard 920e5c93e1 Graph: tooltip fix for single series tooltip and right floated value being pushed down 2014-10-08 17:00:07 -04:00
Torkel Ödegaard 0aae78c6bd Graph: fix for bars not displaying, caused by change in recent commit, added unit test so it does not happen again 2014-10-08 16:24:58 -04:00
Torkel Ödegaard 7fb048f423 Began work on experimental new stats panel 2014-10-08 11:43:51 -04:00
Torkel Ödegaard e86207bb28 Fixed default panel title when creating new panel, tweaks to panel height calculation, Closes #910 2014-10-08 06:44:46 -04:00
Torkel Ödegaard d7cd2b970e OAuth remake 2014-10-07 17:56:37 -04:00
Torkel Ödegaard 450d242d5f working on oauth 2014-10-07 15:54:38 -04:00
Torkel Ödegaard 2783196547 updated 2014-10-07 13:44:20 -04:00
Torkel Ödegaard e84f06b503 more macaroon stuff 2014-10-07 11:53:25 -04:00
Torkel Ödegaard 75d60ccb69 Graphite: added perSecond function to the func def list 2014-10-07 06:40:45 -04:00
Torkel Ödegaard 9245cd6aae Merge branch 'inline_styles' of github.com:mikhailov/grafana into mikhailov-inline_styles 2014-10-07 06:32:13 -04:00
Torkel Ödegaard d8183b60c3 Merge branch 'mikhailov-patch-1' 2014-10-07 06:31:05 -04:00
Torkel Ödegaard a24272690d Merge branch 'patch-1' of github.com:mikhailov/grafana into mikhailov-patch-1 2014-10-07 06:30:53 -04:00
Torkel Ödegaard 40b088d6a2 Merge pull request #900 from mikhailov/patch-2
Speed search result parsing
2014-10-07 06:19:52 -04:00
Torkel Ödegaard f1125d64de Updated default max search results to 100, #909 2014-10-07 06:17:33 -04:00
Torkel Ödegaard 0cba818364 Graph: legend and digest phase fix 2014-10-06 12:37:51 -04:00
Torkel Ödegaard 4285c751b3 Graph: fixed value formating for tooltip, need original data. flotcharts copys the data, so changes to value formats func after plot call does not affect the plot.getData() series 2014-10-06 12:17:48 -04:00
Torkel Ödegaard 1b0cddfa72 Graph: Tooltip refactoring for testability 2014-10-06 11:34:51 -04:00
Torkel Ödegaard 231a599f09 legend html markup cleanup 2014-10-06 11:34:51 -04:00
Torkel Ödegaard 7ffc4d388a Merge pull request #895 from ngmlabs/master
second parameter of groupByNode() should be 0 indexed
2014-10-06 09:50:19 -04:00
Torkel Ödegaard 1f24171238 Merge pull request #902 from toni-moreno/fix_multiple_highlight_points
Fix #901: correct positioning for multiple highlighted stacked and staircase points
2014-10-06 09:27:33 -04:00
toni-moreno 24917a6df5 correct positioning for multiple highlighted stacked and staircase points, fix to #901 2014-10-06 06:22:55 +02:00
Anatoly Mikhailov 67fde17209 Speed searching up a bit
Reuse ElasticSearch hits length via hits.total and cache it rahter than calculation length every iteration.
2014-10-05 23:24:41 +01:00
Anatoly Mikhailov 6abad666db Debounce/Throttling Searching event
New backend call to each keydown may hit the back-end performance.
500ms is reasonable delay to avoid too many requests while user is
typing a search query
2014-10-05 20:54:53 +01:00
Torkel Ödegaard 222319d924 macaron transition progress 2014-10-05 21:13:07 +02:00
mikhailov 2d3f396571 Extract repetitive CSS inline styles
Inline styles slow down rendering proportionally to containers number
2014-10-05 18:30:22 +01:00
Torkel Ödegaard 201e1d3e6d Macaron rewrite 2014-10-05 16:50:04 +02:00
Torkel Ödegaard 2c72831be1 before macaron 2014-10-05 15:34:24 +02:00
Torkel Ödegaard a4204880e8 started work on new arch 2014-10-04 13:33:20 +02:00
George Negoita 51bcbdac75 second parameter of groupByNode() is 0 indexed 2014-10-04 02:33:36 +03:00
Torkel Ödegaard 5d120de70e Filtered logging and url query helper 2014-10-03 09:03:16 +02:00
Torkel Ödegaard e63889d5c4 Css fix for metrics tab, help boxes did extend page height so could not scroll if they extended below screen, #891 2014-10-03 07:31:27 +02:00
Torkel Ödegaard fe6a7c58bf Fixed issue casued by recent angular upgrade and stateful filter, casued template vars in graph titles no update properly, angular was not updated in 1.8.1 so this does affect any released version, Closes #892 2014-10-02 21:48:00 +02:00
Torkel Ödegaard 5f1b4e6183 added ldap dev docker file 2014-10-02 13:42:25 +02:00
Torkel Ödegaard 1e81f8ecfc Merge branch 'master' of github.com:torkelo/grafana-private into pro 2014-10-02 12:26:20 +02:00
Torkel Ödegaard dac3cb15c4 Merge branch 'v1.8.x' 2014-10-02 12:17:37 +02:00
Torkel Ödegaard ca654ccaf7 fixed stupid unit test mistake 2014-10-02 12:17:10 +02:00
Torkel Ödegaard 30512b7032 Merge fix from 'v1.8.x' branch
Conflicts:
	CHANGELOG.md
2014-10-02 12:15:21 +02:00
Torkel Ödegaard bc8fd62cff Dashboard: fix for dashboard schema upgrade during load, ensure that annotation and templating object as a list array, Closes #890 2014-10-02 12:13:18 +02:00
Torkel Ödegaard a9a51ee3c6 Fix to new checkboxes in features toggles tab 2014-10-02 11:54:06 +02:00
Torkel Ödegaard 28c5fc48ee updated sharePanel with width & height 2014-10-02 11:18:26 +02:00
Torkel Ödegaard 2d2da7c881 Small tweak to shared tooltip edit option in Display styles tab 2014-10-02 10:55:08 +02:00
Torkel Ödegaard d22d8c4905 Added grafana logo to repo instead of fetching it from grafana.org, Fixes #881 2014-10-02 10:24:18 +02:00
Torkel Ödegaard bcdc8eafa6 Updated gitter badge 2014-10-02 10:13:23 +02:00
Torkel Ödegaard 9cf6ace979 Updated gitter badge 2014-10-02 10:09:16 +02:00
Torkel Ödegaard eef063cec2 Merge pull request #889 from gitter-badger/gitter-badge
Add a Gitter chat badge to README.md
2014-10-02 10:05:01 +02:00
The Gitter Badger 8b9bdf9054 Added Gitter badge 2014-10-02 07:59:43 +00:00
Torkel Ödegaard 05f9e5eef1 rendering 2014-10-01 21:07:58 +02:00
Torkel Ödegaard eb28e63c08 updated 2014-10-01 20:02:42 +02:00
Torkel Ödegaard 1c7b898b01 Dashboard: show error when importing dashboard json file with broken/incorrect json syntax, Closes #878 2014-10-01 13:56:57 +02:00
Torkel Ödegaard 7ad18da08e update to test helper 2014-10-01 13:49:36 +02:00
Torkel Ödegaard 5530915b49 Updated AngularJS from 1.3-beta-17 to 1.3-RC3 2014-10-01 13:44:45 +02:00
Torkel Ödegaard 4a73e2d0e9 Solo panel and phantom rendering work 2014-10-01 13:20:30 +02:00
Torkel Ödegaard b499bdea3b working on panel rendering 2014-10-01 11:37:04 +02:00
Torkel Ödegaard de1de852fc worked on solo panel 2014-10-01 11:27:58 +02:00
Torkel Ödegaard 77f380c94b Graph: shared tooltip improvements, info tooltip when point counts are not the same, #850 2014-10-01 10:30:24 +02:00
Torkel Ödegaard c34d2f91cc Merge branch 'master' of github.com:torkelo/grafana-private into pro 2014-10-01 09:47:44 +02:00
Torkel Ödegaard ee42ea5f3b merged with master 2014-10-01 09:47:32 +02:00
Torkel Ödegaard c79ab84fdf More fixes related to shared crosshair #880 2014-10-01 09:42:42 +02:00
Torkel Ödegaard d77448d84e Fixed unit tests for grafanaGraph component 2014-10-01 09:10:49 +02:00
Torkel Ödegaard 8fc5a2785f Refactoring and fixes for PR #880 2014-10-01 09:08:11 +02:00
Torkel Ödegaard 27da2b026f Merge branch 'add_shared_croshair_to_dashboards' of github.com:toni-moreno/grafana into toni-moreno-add_shared_croshair_to_dashboards 2014-10-01 08:00:20 +02:00
Torkel Ödegaard 2e9cc2a74e Fix for SharePanelCtrl-specs and travis-ci timezone issue 2014-10-01 07:58:31 +02:00
toni-moreno ffd370176d fix tooltip test expecting for stacked data 2014-10-01 07:15:20 +02:00
toni-moreno 44f2a375f6 Added shared crosshair to dashboards to track better all graphs, and little Fix for #884 2014-10-01 07:03:59 +02:00
Torkel Ödegaard 05cb97819c Updated changelog 2014-09-30 18:21:19 +02:00
Torkel Ödegaard 282c834d9f updated package.json 2014-09-30 17:58:02 +02:00
Torkel Ödegaard 4a6ff9e2aa Merge branch 'v1.8.x' 2014-09-30 17:57:30 +02:00
Torkel Ödegaard ce972d4f19 Graph: css & scroll fix for dropdown menus in graph edit mode, Closes #855 2014-09-30 17:56:43 +02:00
Torkel Ödegaard b250d10320 Trying to fox unit tests for sharePanelCtrl 2014-09-30 17:55:07 +02:00
Torkel Ödegaard 8a2541c220 Merge branch 'master' of github.com:torkelo/grafana-private into pro 2014-09-30 15:35:20 +02:00
Torkel Ödegaard 6991e5f3ef updated 2014-09-30 15:34:31 +02:00
Torkel Ödegaard f63706d118 Small update to share panel 2014-09-30 15:31:18 +02:00
Torkel Ödegaard c41aa64719 ShareModal: Added template variables to share url, and an option for it, #864 2014-09-30 15:19:48 +02:00
Torkel Ödegaard 285d246c65 PanelMenu: fixed interpolate template vars for new panel title/menu 2014-09-30 14:48:36 +02:00
Torkel Ödegaard 2c85205259 SharePanelModal: working on share feature, #864 2014-09-30 14:42:59 +02:00
Torkel Ödegaard 2d866b9298 Updated changelog with the new panel menu, Closes #770 2014-09-30 10:49:59 +02:00
Torkel Ödegaard 7a7629acf7 Panel-menu: small tweeks to css for cursor types 2014-09-30 10:43:13 +02:00
Torkel Ödegaard debf820037 New checkbox change is complete 2014-09-30 10:27:56 +02:00
Torkel Ödegaard f908ae8c40 added checkbox image for white theme 2014-09-30 09:50:01 +02:00
Torkel Ödegaard 022cbdda31 Merge branch 'master' into panel_edit_menu_poc 2014-09-30 09:25:19 +02:00
Torkel Ödegaard ae2523aa59 Merge branch 'v1.8.x' 2014-09-30 09:23:07 +02:00
Torkel Ödegaard bf361d2b02 Updated package.json version and latest.json, preparation for 1.8.1 release 2014-09-30 09:03:14 +02:00
Torkel Ödegaard 64f3303711 InfluxDB: save dashboard issue, another fix for #859 2014-09-30 09:02:25 +02:00
Torkel Ödegaard 06f382c454 Merge branch 'master' of github.com:grafana/grafana 2014-09-29 16:59:54 +02:00
Torkel Ödegaard 5f164d99ac Updated changelog with merged PR #850, shared multi series graph tooltip & crosshair 2014-09-29 16:59:17 +02:00
Torkel Ödegaard 2473ae3b47 Graph: shared multi series tooltip, refactoring PR #850 2014-09-29 16:57:05 +02:00
Torkel Ödegaard 3fb457ccd1 Merge branch 'master' into toni-moreno-add_shared_tooltips_to_graphs 2014-09-29 14:25:25 +02:00
toni-moreno 51333c9eda improved tooltip styles, add multiple highlight points, and changed highligth size when graph plotted without points. 2014-09-29 12:48:08 +02:00
Torkel Ödegaard 1aaf3961ff Merge pull request #867 from tobym/patch-1
Fix typo
2014-09-29 12:43:48 +02:00
Torkel Ödegaard af4f3f62e9 Merged yaxis_precision branch, #877, updated changelog 2014-09-29 12:38:52 +02:00
Torkel Ödegaard cc31a12b8c Smart decimal precision when using scaled unit format, Closes #877 2014-09-29 12:29:53 +02:00
Torkel Ödegaard bc9989f9be replaced checkbox options with the new editor-opt-bool directive 2014-09-27 10:47:48 +02:00
toni-moreno 7f33bec71c fixing the previous fix, now working fine 2014-09-25 15:45:18 +02:00
toni-moreno 3ea94c3484 little fix when searching x index time 2014-09-25 06:35:49 +02:00
Toby Matejovsky 68adaea128 Fix typo 2014-09-24 19:01:23 -04:00
Torkel Ödegaard 4997068a0d Added directive element for the new checkboxes 2014-09-24 19:10:00 +02:00
Torkel Ödegaard 4c59ec815e work on share panel view, and better look for checkbox 2014-09-24 18:55:55 +02:00
Torkel Ödegaard 440ea666d9 more work on share panel, #864 2014-09-24 17:15:58 +02:00
Torkel Ödegaard 6f1a6d5a56 Changed name of emitAppEvent to just appEvent 2014-09-24 16:26:39 +02:00
Torkel Ödegaard 69e80fd11c working on share feature, and refactorings 2014-09-24 16:20:55 +02:00
Torkel Ödegaard bef8cc2d70 Merge branch 'master' into panel_edit_menu_poc 2014-09-24 14:00:55 +02:00
Torkel Ödegaard eaa899e9cf Small fix to fullscreen mode where scrolling would scroll the background dashboard & page header 2014-09-24 14:00:44 +02:00
Torkel Ödegaard 743c95d0f9 small fix to panel menu positioning 2014-09-24 12:45:03 +02:00
Torkel Ödegaard a08cb52ad9 tweaks to new panel menu, now how top area of panel is clickable, and if no title a 5px high area can be clicked, this will enable panels without title which is something I have wanted 2014-09-24 12:41:55 +02:00
Torkel Ödegaard b9604bf3bc Merge branch 'master' into panel_edit_menu_poc 2014-09-24 12:23:04 +02:00
Torkel Ödegaard 978a345ad8 Dashboard: When deleting dashboard show dashboard title in confirmation popup, Closes #860 2014-09-24 12:20:27 +02:00
Torkel Ödegaard d5ffe6acef White theme: Fixes for hidden series legend text and disabled annotations color, Closes #852 2014-09-24 12:14:20 +02:00
Torkel Ödegaard 622c1a1dad small tweak to new panel edit menu 2014-09-24 11:58:02 +02:00
Torkel Ödegaard 79fea549ef Merge branch 'master' into panel_edit_menu_poc 2014-09-24 11:41:25 +02:00
Torkel Ödegaard bce6e75cfa InfluxDB: Fix for bug when saving dashboard where title is the same as slugified url id, Fixes #859 2014-09-24 11:35:08 +02:00
Torkel Ödegaard 34f36fff5c small fix for graphite-web import 2014-09-24 11:17:34 +02:00
Torkel Ödegaard f4e24038fe Import: Fixes to import from json file and import from graphite. Issues was lingering state from previous dashboard. Closes #840, Closes #853 2014-09-24 10:51:20 +02:00
Torkel Ödegaard 81747e1623 Annotations: Fix for annotations not reloaded when switching between 2 dashboards with annotations, Fixes #851 2014-09-24 09:03:04 +02:00
Torkel Ödegaard d6f1c379c0 fixed text editor & scope issue 2014-09-23 22:30:01 +02:00
Torkel Ödegaard 6794260e3f Lots of progress on new panel edit menu, very tricky to get this right but think I am getting close to something that is good and will work long term 2014-09-23 22:10:10 +02:00
Torkel Ödegaard 1be840f19d More wort on panel edit menu 2014-09-23 16:11:31 +02:00
toni-moreno f59bb6461a added shared tooltips to graphs 2014-09-23 13:51:59 +02:00
Torkel Ödegaard bd3bae3af0 another attempt at improving panel edit menu 2014-09-23 10:52:31 +02:00
Torkel Ödegaard 0fbace7285 Row: fix for row editor and scroll pos, Fixes #846 2014-09-23 08:32:04 +02:00
Torkel Ödegaard af8fec941c Graph: Fix for series draw order not being the same after hiding/unhiding series, Fixes #847 2014-09-23 08:18:59 +02:00
Torkel Ödegaard 139791b0d8 Merge branch 'master' into panel_edit_menu_poc 2014-09-22 18:11:52 +02:00
Torkel Ödegaard 7fe76d32d0 updated latest.json 2014-09-22 13:06:52 +02:00
Torkel Ödegaard 352ad3385a Updated changelog and package.json to new version 1.8.0 2014-09-22 12:59:21 +02:00
Torkel Ödegaard 010baad532 Dashboard: fixed init of editable setting, #837 2014-09-22 12:54:02 +02:00
Torkel Ödegaard 69d2a8f4fa style change to login page 2014-09-22 12:31:38 +02:00
Torkel Ödegaard 3bba8b2c26 Github oauth login works 2014-09-22 11:39:40 +02:00
Torkel Ödegaard d584076b93 added github oauth sign in 2014-09-22 11:39:18 +02:00
Torkel Ödegaard b0b77d667c added gravatar 2014-09-22 10:46:56 +02:00
Torkel Ödegaard 071ac0dc85 added gravatar 2014-09-22 10:46:38 +02:00
Torkel Ödegaard ed8f5dbd22 Lots of progress on account management 2014-09-21 15:02:19 +02:00
Torkel Ödegaard 5dfeddf583 Lots of progress on account management 2014-09-21 15:02:06 +02:00
Torkel Ödegaard cd9306df45 Lots of progress on account management 2014-09-21 15:01:59 +02:00
Torkel Ödegaard e0dc530e94 Merge branch 'master' of github.com:torkelo/grafana-private into pro 2014-09-21 08:25:54 +02:00
Torkel Ödegaard af52b20c4a Merge branch 'master' of github.com:grafana/grafana 2014-09-21 08:21:31 +02:00
Torkel Ödegaard e82d171041 Dashboard: when opening search or dashboard settings, click the icon again will now hide the view, Closes #836 2014-09-21 08:19:41 +02:00
Torkel Ödegaard 19a35673fa updated 2014-09-20 16:55:54 +02:00
Torkel Ödegaard 639a44d996 accounts progress 2014-09-20 16:55:38 +02:00
Torkel Ödegaard 4f261389db changed placement of color selector popup 2014-09-20 16:55:02 +02:00
Torkel Ödegaard 6003fee33f Merge branch 'master' into panel_edit_menu_poc 2014-09-20 13:37:02 +02:00
Torkel Ödegaard b56c3eb035 Changed color for warning alert 2014-09-20 13:32:26 +02:00
Torkel Ödegaard 015c929bd7 more account stuff 2014-09-20 13:24:06 +02:00
Torkel Ödegaard 992efda7f6 Account stuff 2014-09-20 12:14:19 +02:00
Torkel Ödegaard 32036d017e Account stuff 2014-09-20 12:14:03 +02:00
Torkel Ödegaard 40ff57d8c4 Account stuff 2014-09-20 12:13:46 +02:00
Torkel Ödegaard cdabe50320 Merge branch 'master' of github.com:torkelo/grafana-pro
Conflicts:
	grafana
2014-09-20 08:58:17 +02:00
Torkel Ödegaard c62ee78cba Merge branch 'pro' of github.com:torkelo/grafana-private into pro
Conflicts:
	src/css/less/grafana.less
	src/test/test-main.js
2014-09-20 08:57:40 +02:00
Torkel Ödegaard a19a2c70ab Fixed spelling in config.sample.js 2014-09-20 08:33:16 +02:00
Torkel Ödegaard 40a491a80b Annotations: Elasticsearch annotation and field mapping fixes, small changes for PR #830 2014-09-20 08:30:59 +02:00
Torkel Ödegaard a9a06ad51d Working on collaborators 2014-09-19 17:37:18 +02:00
Torkel Ödegaard c65b7d1591 updated 2014-09-19 17:36:52 +02:00
Torkel Ödegaard c287405471 Merge branch 'master' of github.com:torkelo/grafana-private into pro 2014-09-19 17:16:34 +02:00
Gregory Becker 06ec91c899 Give maximum width & height constraint to tooltip boxes
Extreme values that go beyond the screen resolution are very likely to be misplaced. This is a simple workaround. A better solution would be to improve the code placing the tooltip and make it handle tooltips containing more content than they can safely display.
2014-09-19 14:16:53 +01:00
Gregory Becker 10f9022d7c Support fields from nested objects pulled from Elasticsearch 2014-09-19 14:11:09 +01:00
Torkel Ödegaard e78c48620f Trying to improve yaxis precision 2014-09-19 13:24:15 +02:00
Torkel Ödegaard 563dd898c1 Graph: fix for downscaling y-axis format, never downscale when value is zero, Fixes #826 2014-09-19 12:16:00 +02:00
Torkel Ödegaard a97bcc3ca7 Elasticsearch: fix for issue when saving dashboard with title equal to slugified url, would cause the backward compatible fix to delete it, Closes #828 2014-09-19 10:37:43 +02:00
Torkel Ödegaard fa31fc046e Merge pull request #823 from beevee/elasticsearch_basic_auth
enable withCredentials in elasticsearch basic auth
2014-09-18 15:54:58 +02:00
Torkel Ödegaard a68a179c1e Small improvements to dashboard alerts, less intrusive, do not push down page anymore, Closes #822 2014-09-18 15:07:49 +02:00
Alexey Kirpichnikov 77b0d36b55 enable withCredentials in elasticsearch basic auth 2014-09-18 17:50:59 +06:00
Torkel Ödegaard 1705734435 updated 2014-09-18 12:04:22 +02:00
Torkel Ödegaard e5fd35db34 small progress on adding collaborators 2014-09-18 12:04:03 +02:00
Torkel Ödegaard 158b708eac Small progress on adding collaborator 2014-09-18 12:03:46 +02:00
Torkel Ödegaard aa47eeffb2 added default default support 2014-09-18 10:16:25 +02:00
Torkel Ödegaard df00ac736f Merge branch 'master' of github.com:torkelo/grafana-private into pro 2014-09-18 09:38:32 +02:00
Torkel Ödegaard f2a6fc4d5a Merge pull request #808 from lento/multiple-stacks
add override options to allow multiple stacks
2014-09-18 09:36:48 +02:00
Torkel Ödegaard c9501d1119 Merge pull request #820 from jozog/master
Handle 'group' graphite method
2014-09-18 09:35:37 +02:00
Torkel Ödegaard 323ff3d491 Merge pull request #821 from linkslice/master
Update playlist.html
2014-09-18 09:35:13 +02:00
Bryan Irvine 98b3126e32 Update playlist.html 2014-09-17 11:04:06 -07:00
Julien Ozog 88ea524f44 Handle 'group' graphite method 2014-09-17 17:12:43 +02:00
Torkel Ödegaard a0ab9113fc Graph: added percent y-axis format, Closes #818 2014-09-17 15:39:45 +02:00
Torkel Ödegaard 31398718e5 Workin on login user state 2014-09-17 15:25:19 +02:00
Torkel Ödegaard 4dfe8b6f69 Working on login user name state 2014-09-17 15:25:07 +02:00
Torkel Ödegaard 6dae8f44b9 Small fix for favicon 2014-09-17 13:58:32 +02:00
Torkel Ödegaard 4f798cfe56 Merge branch 'master' of github.com:torkelo/grafana-private into pro 2014-09-17 13:35:19 +02:00
Torkel Ödegaard d8ea970f18 Added support for delete dashboard 2014-09-17 13:35:01 +02:00
Torkel Ödegaard ecafc7bf8f Added support for delete dashboard 2014-09-17 13:34:42 +02:00
Torkel Ödegaard bba3f3000f Search: remove dashboard from search result after dashboard deletion, Closes #753 2014-09-17 13:00:35 +02:00
Torkel Ödegaard 4b382e0faf Merge branch 'master' of github.com:torkelo/grafana-private into pro
Conflicts:
	src/css/less/grafana.less
	src/test/test-main.js
2014-09-17 09:54:40 +02:00
Torkel Ödegaard 2380a26987 updated 2014-09-17 09:49:28 +02:00
Torkel Ödegaard d40e21a7e0 Chrome: Fix for display issue in chrome beta & chrome canary when entering edit mode, Closes #795 2014-09-17 09:29:51 +02:00
Torkel Ödegaard 94d2ae2a6a Merge branch 'master' of github.com:grafana/grafana 2014-09-17 09:06:37 +02:00
Torkel Ödegaard 3099198e47 Fixed default dashboard grafana logo when using https 2014-09-17 09:03:34 +02:00
Torkel Ödegaard 48e9b5f4be Merge pull request #810 from marcusoftnet/“AddingFavIcon”
Added a favicon. This will resolve issue #796
2014-09-16 13:38:32 +02:00
Torkel Ödegaard 430e2e439b Small fix to schemaUpgrade, Closes #807 2014-09-16 13:32:27 +02:00
Torkel Ödegaard 81a21c03b2 Merge branch 'favicon' 2014-09-16 13:20:42 +02:00
Torkel Odegaard 064a97e734 added favicon, Closes #796 2014-09-16 13:19:52 +02:00
Marcus Hammarberg 5e9ed95684 Added a favicon. This will resolve issue #796 2014-09-16 17:45:43 +07:00
Torkel Ödegaard 017d5617a5 Merge pull request #809 from alxrem/master
fix typos
2014-09-16 11:12:48 +02:00
Alexey Remizov 26bb2e0193 fix typos 2014-09-16 13:08:28 +04:00
Torkel Odegaard ff91430fcc added favicons 2014-09-16 08:01:18 +02:00
Lorenzo Pierfederici 32a41a8422 add override options to allow multiple stacks 2014-09-15 18:12:20 -07:00
Torkel Ödegaard 8b93e20a0b Merge pull request #806 from starshayayord/master
Disable annoying Google Translate plugin
2014-09-15 19:15:57 +02:00
starshayayord 92bec31ccb Update index.html
disable google translate plugin
2014-09-15 18:51:58 +06:00
Torkel Ödegaard f133a9de79 Merge branch 'master' of github.com:torkelo/grafana-pro
Conflicts:
	grafana
2014-09-13 18:36:40 +02:00
Torkel Ödegaard 9b2476451e Merge branch 'pro' of github.com:torkelo/grafana-private into pro
Conflicts:
	src/test/test-main.js
2014-09-13 18:36:17 +02:00
Torkel Ödegaard b876d193c4 updated 2014-09-13 18:33:52 +02:00
Torkel Ödegaard 3944c37627 patches from master 2014-09-13 18:33:35 +02:00
Torkel Ödegaard 14080d1531 Merge remote-tracking branch 'origin/master' into pro
Conflicts:
	src/css/less/grafana.less
	src/test/test-main.js
2014-09-13 18:10:14 +02:00
Torkel Ödegaard 96a0d0aefa fixed changelog 2014-09-13 16:38:01 +02:00
Torkel Ödegaard 15f2b2cf9a Annotations: fixed InfluxDB annotation query, added unit test for annotation query, Fixes #802 2014-09-13 16:19:33 +02:00
Torkel Ödegaard bf9eaea334 Updated lastest.json 2014-09-12 13:19:17 +02:00
Torkel Ödegaard 7b45a2ec51 Small fix to scripted async dashboard example 2014-09-12 13:15:06 +02:00
Torkel Ödegaard a6fa01f89b POC of panel edit menu 2014-09-06 14:05:07 +02:00
Torkel Ödegaard c00384ad06 work on account registration 2014-08-22 18:05:51 +02:00
Torkel Ödegaard 31fe471da5 working on account registration and more 2014-08-22 18:05:37 +02:00
Torkel Ödegaard c684b1ddab dashboard load/save work and began work on register 2014-08-22 15:32:42 +02:00
Torkel Ödegaard 477f66f56b dashobard load/save work, and began register view 2014-08-22 15:32:20 +02:00
Torkel Ödegaard 359bca0634 save/load dashboard update 2014-08-22 13:25:22 +02:00
Torkel Ödegaard 07d8b542bf working on rethinkdb stuff 2014-08-21 22:09:48 +02:00
Torkel Ödegaard 06de80f2a7 updated 2014-08-21 22:09:26 +02:00
Torkel Ödegaard ef351bb9a3 Merge branch 'master' of github.com:torkelo/grafana-private into pro 2014-08-21 22:01:32 +02:00
Torkel Ödegaard 37e19cee20 fixed jshint error 2014-08-21 11:18:49 +02:00
Torkel Ödegaard b39fbff8fa Merge remote-tracking branch 'origin/pro' into pro
Conflicts:
	src/test/test-main.js
2014-08-21 11:17:44 +02:00
Torkel Ödegaard 5ff15f27b7 Merge 2014-08-21 11:16:22 +02:00
Torkel Ödegaard baf99e8650 Merge branch 'master' of github.com:torkelo/grafana-private into pro
Conflicts:
	src/test/test-main.js
2014-08-21 11:16:08 +02:00
Torkel Ödegaard 631c7adf12 updated 2014-08-17 12:26:00 +02:00
Torkel Ödegaard d8dca20332 updated 2014-08-17 12:25:37 +02:00
Torkel Ödegaard efed13b9fe Work on login and sidemenu 2014-08-16 21:54:26 +02:00
Torkel Ödegaard 34ab1e529b Lots of work on the side menu 2014-08-16 21:54:05 +02:00
Torkel Ödegaard c892d9d6f6 Merge remote-tracking branch 'origin/master' into pro 2014-08-16 13:15:02 +02:00
Torkel Ödegaard 9951c3825e began work on pro side menu 2014-08-16 10:14:31 +02:00
Torkel Ödegaard 9ac1f6b26b Merge remote-tracking branch 'origin/move_stuff' into pro 2014-08-16 08:56:19 +02:00
Torkel Ödegaard dac4954215 merged with oss 2014-08-16 08:56:02 +02:00
Torkel Ödegaard 9e30599f1f rename backend to pkg 2014-08-12 20:45:41 +02:00
Torkel Ödegaard 472e1c6d8e fixed tests 2014-08-12 20:15:03 +02:00
Torkel Ödegaard 46edd6008f merge sync oss 2014-08-12 20:06:33 +02:00
Torkel Ödegaard e7ce371ee8 working on login && auth 2014-08-12 15:07:20 +02:00
Torkel Ödegaard bfe1ef0733 worked on login view 2014-08-12 15:06:54 +02:00
Torkel Ödegaard 56d449b2ce updated 2014-08-12 12:20:15 +02:00
Torkel Ödegaard c690d4aae8 added pro routes 2014-08-12 12:19:33 +02:00
Torkel Ödegaard 4966b10a0f pro changes 2014-08-12 12:19:15 +02:00
Torkel Ödegaard 24579d86d2 updated 2014-08-12 07:25:02 +02:00
Torkel Ödegaard 71573fb5a0 restructing api code 2014-08-11 20:18:46 +02:00
Torkel Ödegaard 3a5f718da8 updated 2014-08-11 16:37:44 +02:00
Torkel Ödegaard 8699dec571 fixed jshint issues 2014-08-11 16:37:31 +02:00
Torkel Ödegaard 6f0c6281e7 Merge remote-tracking branch 'origin/master' into pro 2014-08-11 16:09:40 +02:00
Torkel Ödegaard 37b3b1fc3a Merge remote-tracking branch 'origin/master' into pro 2014-08-11 15:30:45 +02:00
Torkel Ödegaard 380f707285 workin on png rendering 2014-08-09 11:57:54 +02:00
Torkel Ödegaard 1bc277fd87 Merge branch 'develop' into pro 2014-08-09 10:24:17 +02:00
Torkel Ödegaard c669f8c612 working on rendering panel with phantomjs 2014-08-08 15:53:31 +02:00
Torkel Ödegaard f068b2c1d3 Merge remote-tracking branch 'origin/overview' into pro 2014-08-08 14:37:24 +02:00
Torkel Ödegaard 3dd383ba11 solo panel work 2014-08-08 14:36:13 +02:00
Torkel Ödegaard 3a0983d8bf Merge branch 'develop' into overview 2014-08-08 14:11:11 +02:00
Torkel Ödegaard bc7ef28644 Merge branch 'develop' into overview
Conflicts:
	src/test/test-main.js
2014-08-08 13:48:39 +02:00
Torkel Ödegaard a0780ce48a Began work on solo panel 2014-08-08 13:37:51 +02:00
Torkel Ödegaard 8154b4f60d search and save are 'working', barely 2014-08-08 12:35:15 +02:00
Torkel Ödegaard 91a6ae756f updated grafana datasource 2014-08-08 12:34:28 +02:00
Torkel Ödegaard eb6099a93f Began work on pro modification 2014-08-07 16:04:54 +02:00
Torkel Ödegaard c416b09089 more work on overview panel 2014-08-07 14:09:07 +02:00
Torkel Ödegaard f144593c06 Merge branch 'develop' into overview 2014-08-07 13:45:01 +02:00
Torkel Ödegaard 5337756792 working on overview panel and unit tests 2014-08-07 10:12:31 +02:00
Torkel Ödegaard 8ed4859bb5 Merge branch 'develop' into overview 2014-08-07 09:01:24 +02:00
Torkel Ödegaard 1e7beffafd more unit test work 2014-08-06 19:33:38 +02:00
Torkel Ödegaard c2c16b6bd2 Merge branch 'develop' into overview 2014-08-06 17:26:52 +02:00
Torkel Ödegaard 72e5a40e7e began work on overview panel 2014-08-06 16:09:26 +02:00
1246 changed files with 351697 additions and 21945 deletions
+17
View File
@@ -0,0 +1,17 @@
[run]
init_cmds = [
["go", "build", "-o", "./bin/grafana-server"],
["./bin/grafana-server"]
]
watch_all = true
watch_dirs = [
"$WORKDIR/pkg",
"$WORKDIR/public/views",
"$WORKDIR/conf",
]
watch_exts = [".go", ".ini"]
build_delay = 1500
cmds = [
["go", "build", "-o", "./bin/grafana-server"],
["./bin/grafana-server"]
]
+19 -4
View File
@@ -1,14 +1,29 @@
node_modules
coverage/
.aws-config.json
dist
awsconfig
/dist
/tmp
docs/AWS_S3_BUCKET
docs/GIT_BRANCH
docs/VERSION
docs/GITCOMMIT
docs/changed-files
docs/changed-files
# locally required config files
web.config
config.js
src/css/*.min.css
public/css/*.min.css
# Editor junk
*.sublime-workspace
*.swp
.idea/
*.iml
/data/*
/bin/*
conf/custom.ini
fig.yml
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
test -z "$(gofmt -s -l . | grep -v Godeps/_workspace/src/ | tee /dev/stderr)"
if [ $? -gt 0 ]; then
echo "Some files aren't formatted, please run 'go fmt ./pkg/...' to format your source code before committing"
exit 1
fi
+21
View File
@@ -0,0 +1,21 @@
{
"preset" : "default",
"lineBreak" : {
"before" : {
"VariableDeclarationWithoutInit" : 0,
},
"after": {
"AssignmentOperator": -1,
"ArgumentListArrayExpression": ">=1"
}
},
"whiteSpace" : {
"before" : {
},
"after" : {
}
}
}
-9
View File
@@ -1,9 +0,0 @@
language: node_js
node_js:
- "0.10"
git:
depth: 1
before_script:
- npm install -g grunt-cli
after_script:
- npm run coveralls
+195 -2
View File
@@ -1,4 +1,197 @@
# 1.8.0 (unreleased)
# 2.0.3 (unreleased)
**Fixes**
- [Issue #1872](https://github.com/grafana/grafana/issues/1872). Firefox/IE issue, invisible text in dashboard search fixed
- [Issue #1857](https://github.com/grafana/grafana/issues/1857). /api/login/ping Fix for issue when behind reverse proxy and subpath
- [Issue #1863](https://github.com/grafana/grafana/issues/1863). MySQL: Dashboard.data column type changed to mediumtext (sql migration added)
# 2.0.2 (2015-04-22)
**Fixes**
- [Issue #1832](https://github.com/grafana/grafana/issues/1832). Graph Panel + Legend Table mode: Many series casued zero height graph, now legend will never reduce the height of the graph below 50% of row height.
- [Issue #1846](https://github.com/grafana/grafana/issues/1846). Snapshots: Fixed issue with snapshoting dashboards with an interval template variable
- [Issue #1848](https://github.com/grafana/grafana/issues/1848). Panel timeshift: You can now use panel timeshift without a relative time override
# 2.0.1 (2015-04-20)
**Fixes**
- [Issue #1784](https://github.com/grafana/grafana/issues/1784). Data source proxy: Fixed issue with using data source proxy when grafana is behind nginx suburl
- [Issue #1749](https://github.com/grafana/grafana/issues/1749). Graph Panel: Table legends are now visible when rendered to PNG
- [Issue #1786](https://github.com/grafana/grafana/issues/1786). Graph Panel: Legend in table mode now aligns, graph area is reduced depending on how many series
- [Issue #1734](https://github.com/grafana/grafana/issues/1734). Support for unicode / international characters in dashboard title (improved slugify)
- [Issue #1782](https://github.com/grafana/grafana/issues/1782). Github OAuth: Now works with Github for Enterprise, thanks @williamjoy
- [Issue #1780](https://github.com/grafana/grafana/issues/1780). Dashboard snapshot: Should not require login to view snapshot, Fixes #1780
# 2.0.0-Beta3 (2015-04-12)
**RPM / DEB Package changes (to follow HFS)**
- binary name changed to grafana-server
- does not install to `/opt/grafana` any more, installs to `/usr/share/grafana`
- binary to `/usr/sbin/grafana-server`
- init.d script improvements, renamed to `/etc/init.d/grafana-server`
- added default file with environment variables,
- `/etc/default/grafana-server` (deb/ubuntu)
- `/etc/sysconfig/grafana-server` (centos/redhat)
- added systemd service file, tested on debian jessie and centos7
- config file in same location `/etc/grafana/grafana.ini` (now complete config file but with every setting commented out)
- data directory (where sqlite3) file is stored is now by default `/var/lib/grafana`
- no symlinking current to versions anymore
- For more info see [Issue #1758](https://github.com/grafana/grafana/issues/1758).
**Config breaking change (setting rename)**
- `[log] root_path` has changed to `[paths] logs`
# 2.0.0-Beta2 (...)
**Enhancements**
- [Issue #1701](https://github.com/grafana/grafana/issues/1701). Share modal: Override UI theme via URL param for Share link, rendered panel, or embedded panel
- [Issue #1660](https://github.com/grafana/grafana/issues/1660). OAuth: Specify allowed email address domains for google or and github oauth logins
**Fixes**
- [Issue #1649](https://github.com/grafana/grafana/issues/1649). HTTP API: grafana /render calls nows with api keys
- [Issue #1667](https://github.com/grafana/grafana/issues/1667). Datasource proxy & session timeout fix (casued 401 Unauthorized error after a while)
- [Issue #1707](https://github.com/grafana/grafana/issues/1707). Unsaved changes: Do not show for snapshots, scripted and file based dashboards
- [Issue #1703](https://github.com/grafana/grafana/issues/1703). Unsaved changes: Do not show for users with role `Viewer`
- [Issue #1675](https://github.com/grafana/grafana/issues/1675). Data source proxy: Fixed issue with Gzip enabled and data source proxy
- [Issue #1681](https://github.com/grafana/grafana/issues/1681). MySQL session: fixed problem using mysql as session store
- [Issue #1671](https://github.com/grafana/grafana/issues/1671). Data sources: Fixed issue with changing default data source (should not require full page load to take effect, now fixed)
- [Issue #1685](https://github.com/grafana/grafana/issues/1685). Search: Dashboard results should be sorted alphabetically
- [Issue #1673](https://github.com/grafana/grafana/issues/1673). Basic auth: Fixed issue when using basic auth proxy infront of Grafana
# 2.0.0-Beta1 (2015-03-30)
**New features**
- [Issue #1623](https://github.com/grafana/grafana/issues/1623). Share Dashboard: Dashboard snapshot sharing (dash and data snapshot), save to local or save to public snapshot dashboard snapshots.raintank.io site
- [Issue #1622](https://github.com/grafana/grafana/issues/1622). Share Panel: The share modal now has an embed option, gives you an iframe that you can use to embedd a single graph on another web site
- [Issue #718](https://github.com/grafana/grafana/issues/718). Dashboard: When saving a dashboard and another user has made changes inbetween the user is promted with a warning if he really wants to overwrite the other's changes
- [Issue #1331](https://github.com/grafana/grafana/issues/1331). Graph & Singlestat: New axis/unit format selector and more units (kbytes, Joule, Watt, eV), and new design for graph axis & grid tab and single stat options tab views
- [Issue #1241](https://github.com/grafana/grafana/issues/1242). Timepicker: New option in timepicker (under dashboard settings), to change ``now`` to be for example ``now-1m``, usefull when you want to ignore last minute because it contains incomplete data
- [Issue #171](https://github.com/grafana/grafana/issues/171). Panel: Different time periods, panels can override dashboard relative time and/or add a time shift
- [Issue #1488](https://github.com/grafana/grafana/issues/1488). Dashboard: Clone dashboard / Save as
- [Issue #1458](https://github.com/grafana/grafana/issues/1458). User: persisted user option for dark or light theme (no longer an option on a dashboard)
- [Issue #452](https://github.com/grafana/grafana/issues/452). Graph: Adds logarithmic scale option for base 10, base 16 and base 1024
**Enhancements**
- [Issue #1366](https://github.com/grafana/grafana/issues/1366). Graph & Singlestat: Support for additional units, Fahrenheit (°F) and Celsius (°C), Humidity (%H), kW, watt-hour (Wh), kilowatt-hour (kWh), velocities (m/s, km/h, mpg, knot)
- [Issue #978](https://github.com/grafana/grafana/issues/978). Graph: Shared tooltip improvement, can now support metrics of different resolution/intervals
- [Issue #1297](https://github.com/grafana/grafana/issues/1297). Graphite: Added cumulative and minimumBelow graphite functions
- [Issue #1296](https://github.com/grafana/grafana/issues/1296). InfluxDB: Auto escape column names with special characters. Thanks @steven-aerts
- [Issue #1321](https://github.com/grafana/grafana/issues/1321). SingleStatPanel: You can now use template variables in pre & postfix
- [Issue #599](https://github.com/grafana/grafana/issues/599). Graph: Added right y axis label setting and graph support
- [Issue #1253](https://github.com/grafana/grafana/issues/1253). Graph & Singlestat: Users can now set decimal precision for legend and tooltips (override auto precision)
- [Issue #1255](https://github.com/grafana/grafana/issues/1255). Templating: Dashboard will now wait to load until all template variables that have refresh on load set or are initialized via url to be fully loaded and so all variables are in valid state before panels start issuing metric requests.
- [Issue #1344](https://github.com/grafana/grafana/issues/1344). OpenTSDB: Alias patterns (reference tag values), syntax is: $tag_tagname or [[tag_tagname]]
**Fixes**
- [Issue #1298](https://github.com/grafana/grafana/issues/1298). InfluxDB: Fix handling of empty array in templating variable query
- [Issue #1309](https://github.com/grafana/grafana/issues/1309). Graph: Fixed issue when using zero as a grid threshold
- [Issue #1345](https://github.com/grafana/grafana/issues/1345). UI: Fixed position of confirm modal when scrolled down
- [Issue #1372](https://github.com/grafana/grafana/issues/1372). Graphite: Fix for nested complex queries, where a query references a query that references another query (ie the #[A-Z] syntax)
- [Issue #1363](https://github.com/grafana/grafana/issues/1363). Templating: Fix to allow custom template variables to contain white space, now only splits on ','
- [Issue #1359](https://github.com/grafana/grafana/issues/1359). Graph: Fix for all series tooltip showing series with all null values when ``Hide Empty`` option is enabled
- [Issue #1497](https://github.com/grafana/grafana/issues/1497). Dashboard: Fixed memory leak when switching dashboards
**Changes**
- Dashboard title change & save will no longer create a new dashboard, it will just change the title.
**OpenTSDB breaking change**
- [Issue #1438](https://github.com/grafana/grafana/issues/1438). OpenTSDB: Automatic downsample interval passed to OpenTSDB (depends on timespan and graph width)
- NOTICE, Downsampling is now enabled by default, so if you have not picked a downsample aggregator in your metric query do so or your graphs will be missleading
- This will make Grafana a lot quicker for OpenTSDB users when viewing large time spans without having to change the downsample interval manually.
**Tech**
- [Issue #1311](https://github.com/grafana/grafana/issues/1311). Tech: Updated Font-Awesome from 3.2 to 4.2
# 1.9.1 (2014-12-29)
**Enhancements**
- [Issue #1028](https://github.com/grafana/grafana/issues/1028). Graph: New legend option ``hideEmtpy`` to hide series with only null values from legend
- [Issue #1242](https://github.com/grafana/grafana/issues/1242). OpenTSDB: Downsample query field now supports interval template variable
- [Issue #1126](https://github.com/grafana/grafana/issues/1126). InfluxDB: Support more than 10 series name segments when using alias ``$number`` patterns
**Fixes**
- [Issue #1251](https://github.com/grafana/grafana/issues/1251). Graph: Fix for y axis and scaled units (GiB etc) caused rounding, for example 400 GiB instead of 378 GiB
- [Issue #1199](https://github.com/grafana/grafana/issues/1199). Graph: fix for series tooltip when one series is hidden/disabled
- [Issue #1207](https://github.com/grafana/grafana/issues/1207). Graphite: movingAverage / movingMedian parameter type impovement, now handles int and interval parameter
# 1.9.0 (2014-12-02)
**Enhancements**
- [Issue #1130](https://github.com/grafana/grafana/issues/1130). SinglestatPanel: Added null point handling, and value to text mapping
**Fixes**
- [Issue #1087](https://github.com/grafana/grafana/issues/1087). Panel: Fixed IE9 crash due to angular drag drop
- [Issue #1093](https://github.com/grafana/grafana/issues/1093). SingleStatPanel: Fixed position for drilldown link tooltip when dashboard requires scrolling
- [Issue #1095](https://github.com/grafana/grafana/issues/1095). DrilldownLink: template variables in params property was not interpolated
- [Issue #1114](https://github.com/grafana/grafana/issues/1114). Graphite: Lexer fix, allow equal sign (=) in metric paths
- [Issue #1136](https://github.com/grafana/grafana/issues/1136). Graph: Fix to legend value Max and negative values
- [Issue #1150](https://github.com/grafana/grafana/issues/1150). SinglestatPanel: Fixed absolute drilldown link issue
- [Issue #1123](https://github.com/grafana/grafana/issues/1123). Firefox: Workaround for Firefox bug, casued input text fields to not be selectable and not have placeable cursor
- [Issue #1108](https://github.com/grafana/grafana/issues/1108). Graph: Fix for tooltip series order when series draw order was changed with zindex property
# 1.9.0-rc1 (2014-11-17)
**UI Improvements**
- [Issue #770](https://github.com/grafana/grafana/issues/770). UI: Panel dropdown menu replaced with a new panel menu
**Graph**
- [Issue #877](https://github.com/grafana/grafana/issues/877). Graph: Smart auto decimal precision when using scaled unit formats
- [Issue #850](https://github.com/grafana/grafana/issues/850). Graph: Shared tooltip that shows multiple series & crosshair line, thx @toni-moreno
- [Issue #940](https://github.com/grafana/grafana/issues/940). Graph: New series style override option "Fill below to", useful to visualize max & min as a shadow for the mean
- [Issue #1030](https://github.com/grafana/grafana/issues/1030). Graph: Legend table display/look changed, now includes column headers for min/max/avg, and full width (unless on right side)
- [Issue #861](https://github.com/grafana/grafana/issues/861). Graph: Export graph time series data as csv file
**New Panels**
- [Issue #951](https://github.com/grafana/grafana/issues/951). SingleStat: New singlestat panel
**Misc**
- [Issue #864](https://github.com/grafana/grafana/issues/846). Panel: Share panel feature, get a link to panel with the current time range
- [Issue #938](https://github.com/grafana/grafana/issues/938). Panel: Plugin panels now reside outside of app/panels directory
- [Issue #952](https://github.com/grafana/grafana/issues/952). Help: Shortcut "?" to open help modal with list of all shortcuts
- [Issue #991](https://github.com/grafana/grafana/issues/991). ScriptedDashboard: datasource services are now available in scripted dashboards, you can query datasource for metric keys, generate dashboards, and even save them in a scripted dashboard (see scripted_gen_and_save.js for example)
- [Issue #1041](https://github.com/grafana/grafana/issues/1041). Panel: All panels can now have links to other dashboards or absolute links, these links are available in the panel menu.
**Changes**
- [Issue #1007](https://github.com/grafana/grafana/issues/1007). Graph: Series hide/show toggle changed to be default exclusive, so clicking on a series name will show only that series. (SHIFT or meta)+click will toggle hide/show.
**OpenTSDB**
- [Issue #930](https://github.com/grafana/grafana/issues/930). OpenTSDB: Adding counter max and counter reset value to open tsdb query editor, thx @rsimiciuc
- [Issue #917](https://github.com/grafana/grafana/issues/917). OpenTSDB: Templating support for OpenTSDB series name and tags, thx @mchataigner
**InfluxDB**
- [Issue #714](https://github.com/grafana/grafana/issues/714). InfluxDB: Support for sub second resolution graphs
**Fixes**
- [Issue #925](https://github.com/grafana/grafana/issues/925). Graph: bar width calculation fix for some edge cases (bars would render on top of each other)
- [Issue #505](https://github.com/grafana/grafana/issues/505). Graph: fix for second y axis tick unit labels wrapping on the next line
- [Issue #987](https://github.com/grafana/grafana/issues/987). Dashboard: Collapsed rows became invisible when hide controls was enabled
=======
# 1.8.1 (2014-09-30)
**Fixes**
- [Issue #855](https://github.com/grafana/grafana/issues/855). Graph: Fix for scroll issue in graph edit mode when dropdown goes below screen
- [Issue #847](https://github.com/grafana/grafana/issues/847). Graph: Fix for series draw order not being the same after hiding/unhiding series
- [Issue #851](https://github.com/grafana/grafana/issues/851). Annotations: Fix for annotations not reloaded when switching between 2 dashboards with annotations
- [Issue #846](https://github.com/grafana/grafana/issues/846). Edit panes: Issue when open row or json editor when scrolled down the page, unable to scroll and you did not see editor
- [Issue #840](https://github.com/grafana/grafana/issues/840). Import: Fixes to import from json file and import from graphite. Issues was lingering state from previous dashboard.
- [Issue #859](https://github.com/grafana/grafana/issues/859). InfluxDB: Fix for bug when saving dashboard where title is the same as slugified url id
- [Issue #852](https://github.com/grafana/grafana/issues/852). White theme: Fixes for hidden series legend text and disabled annotations color
# 1.8.0 (2014-09-22)
Read this [blog post](http://grafana.org/blog/2014/09/11/grafana-1-8-0-rc1-released.html) for an overview of all improvements.
**Fixes**
- [Issue #802](https://github.com/grafana/grafana/issues/802). Annotations: Fix when using InfluxDB datasource
- [Issue #795](https://github.com/grafana/grafana/issues/795). Chrome: Fix for display issue in chrome beta & chrome canary when entering edit mode
- [Issue #818](https://github.com/grafana/grafana/issues/818). Graph: Added percent y-axis format
- [Issue #828](https://github.com/grafana/grafana/issues/828). Elasticsearch: saving new dashboard with title equal to slugified url would cause it to deleted.
- [Issue #830](https://github.com/grafana/grafana/issues/830). Annotations: Fix for elasticsearch annotations and mapping nested fields
# 1.8.0-RC1 (2014-09-12)
**UI polish / changes**
- [Issue #725](https://github.com/grafana/grafana/issues/725). UI: All modal editors are removed and replaced by an edit pane under menu. The look of editors is also updated and polished. Search dropdown is also shown as pane under menu and has seen some UI polish.
@@ -239,7 +432,7 @@ Read this for more info:
- More graphite function definitions
- Make "ms" axis format include hour, day, weeks, month and year ([Issue #149](https://github.com/grafana/grafana/issues/149))
- Microsecond axis format ([Issue #146](https://github.com/grafana/grafana/issues/146))
- Specify template paramaters in URL ([Issue #123](https://github.com/grafana/grafana/issues/123))
- Specify template parameters in URL ([Issue #123](https://github.com/grafana/grafana/issues/123))
### Fixes
- Basic Auth fix ([Issue #152](https://github.com/grafana/grafana/issues/152))
+96
View File
@@ -0,0 +1,96 @@
{
"ImportPath": "github.com/grafana/grafana",
"GoVersion": "go1.3",
"Packages": [
"./pkg/..."
],
"Deps": [
{
"ImportPath": "github.com/Unknwon/com",
"Rev": "d9bcf409c8a368d06c9b347705c381e7c12d54df"
},
{
"ImportPath": "github.com/Unknwon/macaron",
"Rev": "93de4f3fad97bf246b838f828e2348f46f21f20a"
},
{
"ImportPath": "github.com/dalu/slug",
"Rev": "6dbd13912e9be466e2c1de349a2c7d1466c97e07"
},
{
"ImportPath": "github.com/dalu/unidecode",
"Rev": "339814d47f3e32a6f7036a0a4c56ed9b373dd755"
},
{
"ImportPath": "github.com/go-sql-driver/mysql",
"Comment": "v1.2-26-g9543750",
"Rev": "9543750295406ef070f7de8ae9c43ccddd44e15e"
},
{
"ImportPath": "github.com/go-xorm/core",
"Rev": "be6e7ac47dc57bd0ada25322fa526944f66ccaa6"
},
{
"ImportPath": "github.com/go-xorm/xorm",
"Comment": "v0.4.2-58-ge2889e5",
"Rev": "e2889e5517600b82905f1d2ba8b70deb71823ffe"
},
{
"ImportPath": "github.com/jtolds/gls",
"Rev": "f1ac7f4f24f50328e6bc838ca4437d1612a0243c"
},
{
"ImportPath": "github.com/lib/pq",
"Comment": "go1.0-cutoff-13-g19eeca3",
"Rev": "19eeca3e30d2577b1761db471ec130810e67f532"
},
{
"ImportPath": "github.com/macaron-contrib/binding",
"Rev": "0fbe4b9707e6eb556ef843e5471592f55ce0a5e7"
},
{
"ImportPath": "github.com/macaron-contrib/session",
"Rev": "31e841d95c7302b9ac456c830ea2d6dfcef4f84a"
},
{
"ImportPath": "github.com/mattn/go-sqlite3",
"Rev": "e28cd440fabdd39b9520344bc26829f61db40ece"
},
{
"ImportPath": "github.com/smartystreets/goconvey/convey",
"Comment": "1.5.0-356-gfbc0a1c",
"Rev": "fbc0a1c888f9f96263f9a559d1769905245f1123"
},
{
"ImportPath": "github.com/streadway/amqp",
"Rev": "150b7f24d6ad507e6026c13d85ce1f1391ac7400"
},
{
"ImportPath": "golang.org/x/net/context",
"Rev": "972f0c5fbe4ae29e666c3f78c3ed42ae7a448b0a"
},
{
"ImportPath": "golang.org/x/oauth2",
"Rev": "c58fcf0ffc1c772aa2e1ee4894bc19f2649263b2"
},
{
"ImportPath": "gopkg.in/bufio.v1",
"Comment": "v1",
"Rev": "567b2bfa514e796916c4747494d6ff5132a1dfce"
},
{
"ImportPath": "gopkg.in/ini.v1",
"Comment": "v0-16-g1772191",
"Rev": "177219109c97e7920c933e21c9b25f874357b237"
},
{
"ImportPath": "gopkg.in/redis.v2",
"Comment": "v2.3.2",
"Rev": "e6179049628164864e6e84e973cfb56335748dea"
},
{
"ImportPath": "gopkgs.com/pool.v1",
"Rev": "c850f092aad1780cbffff25f471c5cc32097932a"
}
]
}
Generated
+5
View File
@@ -0,0 +1,5 @@
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.
+2
View File
@@ -0,0 +1,2 @@
/pkg
/bin
+24
View File
@@ -0,0 +1,24 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
.idea
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.iml
+191
View File
@@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "[]" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+24
View File
@@ -0,0 +1,24 @@
Common functions
===
[![Build Status](https://drone.io/github.com/Unknwon/com/status.png)](https://drone.io/github.com/Unknwon/com/latest) [![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/Unknwon/com)
This is an open source project for commonly used functions for the Go programming language.
This package need >= **go 1.2**
Code Convention: based on [Go Code Convention](https://github.com/Unknwon/go-code-convention).
## Contribute
Your contribute is welcome, but you have to check following steps after you added some functions and commit them:
1. Make sure you wrote user-friendly comments for **all functions** .
2. Make sure you wrote test cases with any possible condition for **all functions** in file `*_test.go`.
3. Make sure you wrote benchmarks for **all functions** in file `*_test.go`.
4. Make sure you wrote useful examples for **all functions** in file `example_test.go`.
5. Make sure you ran `go test -bench="."` and got **PASS** .
## Performance
See results on [drone.io](https://drone.io/github.com/Unknwon/com/latest) by `go test -bench="."`.
+161
View File
@@ -0,0 +1,161 @@
// +build go1.2
// Copyright 2013 com authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
// Package com is an open source project for commonly used functions for the Go programming language.
package com
import (
"bytes"
"fmt"
"os/exec"
"runtime"
"strings"
)
// ExecCmdDirBytes executes system command in given directory
// and return stdout, stderr in bytes type, along with possible error.
func ExecCmdDirBytes(dir, cmdName string, args ...string) ([]byte, []byte, error) {
bufOut := new(bytes.Buffer)
bufErr := new(bytes.Buffer)
cmd := exec.Command(cmdName, args...)
cmd.Dir = dir
cmd.Stdout = bufOut
cmd.Stderr = bufErr
err := cmd.Run()
return bufOut.Bytes(), bufErr.Bytes(), err
}
// ExecCmdBytes executes system command
// and return stdout, stderr in bytes type, along with possible error.
func ExecCmdBytes(cmdName string, args ...string) ([]byte, []byte, error) {
return ExecCmdDirBytes("", cmdName, args...)
}
// ExecCmdDir executes system command in given directory
// and return stdout, stderr in string type, along with possible error.
func ExecCmdDir(dir, cmdName string, args ...string) (string, string, error) {
bufOut, bufErr, err := ExecCmdDirBytes(dir, cmdName, args...)
return string(bufOut), string(bufErr), err
}
// ExecCmd executes system command
// and return stdout, stderr in string type, along with possible error.
func ExecCmd(cmdName string, args ...string) (string, string, error) {
return ExecCmdDir("", cmdName, args...)
}
// _________ .__ .____
// \_ ___ \ ____ | | ___________ | | ____ ____
// / \ \/ / _ \| | / _ \_ __ \ | | / _ \ / ___\
// \ \___( <_> ) |_( <_> ) | \/ | |__( <_> ) /_/ >
// \______ /\____/|____/\____/|__| |_______ \____/\___ /
// \/ \/ /_____/
// Color number constants.
const (
Gray = uint8(iota + 90)
Red
Green
Yellow
Blue
Magenta
//NRed = uint8(31) // Normal
EndColor = "\033[0m"
)
// getColorLevel returns colored level string by given level.
func getColorLevel(level string) string {
level = strings.ToUpper(level)
switch level {
case "TRAC":
return fmt.Sprintf("\033[%dm%s\033[0m", Blue, level)
case "ERRO":
return fmt.Sprintf("\033[%dm%s\033[0m", Red, level)
case "WARN":
return fmt.Sprintf("\033[%dm%s\033[0m", Magenta, level)
case "SUCC":
return fmt.Sprintf("\033[%dm%s\033[0m", Green, level)
default:
return level
}
}
// ColorLogS colors log and return colored content.
// Log format: <level> <content [highlight][path]> [ error ].
// Level: TRAC -> blue; ERRO -> red; WARN -> Magenta; SUCC -> green; others -> default.
// Content: default; path: yellow; error -> red.
// Level has to be surrounded by "[" and "]".
// Highlights have to be surrounded by "# " and " #"(space), "#" will be deleted.
// Paths have to be surrounded by "( " and " )"(space).
// Errors have to be surrounded by "[ " and " ]"(space).
// Note: it hasn't support windows yet, contribute is welcome.
func ColorLogS(format string, a ...interface{}) string {
log := fmt.Sprintf(format, a...)
var clog string
if runtime.GOOS != "windows" {
// Level.
i := strings.Index(log, "]")
if log[0] == '[' && i > -1 {
clog += "[" + getColorLevel(log[1:i]) + "]"
}
log = log[i+1:]
// Error.
log = strings.Replace(log, "[ ", fmt.Sprintf("[\033[%dm", Red), -1)
log = strings.Replace(log, " ]", EndColor+"]", -1)
// Path.
log = strings.Replace(log, "( ", fmt.Sprintf("(\033[%dm", Yellow), -1)
log = strings.Replace(log, " )", EndColor+")", -1)
// Highlights.
log = strings.Replace(log, "# ", fmt.Sprintf("\033[%dm", Gray), -1)
log = strings.Replace(log, " #", EndColor, -1)
} else {
// Level.
i := strings.Index(log, "]")
if log[0] == '[' && i > -1 {
clog += "[" + log[1:i] + "]"
}
log = log[i+1:]
// Error.
log = strings.Replace(log, "[ ", "[", -1)
log = strings.Replace(log, " ]", "]", -1)
// Path.
log = strings.Replace(log, "( ", "(", -1)
log = strings.Replace(log, " )", ")", -1)
// Highlights.
log = strings.Replace(log, "# ", "", -1)
log = strings.Replace(log, " #", "", -1)
}
return clog + log
}
// ColorLog prints colored log to stdout.
// See color rules in function 'ColorLogS'.
func ColorLog(format string, a ...interface{}) {
fmt.Print(ColorLogS(format, a...))
}
+140
View File
@@ -0,0 +1,140 @@
// Copyright 2013 com authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package com
import (
"fmt"
"runtime"
"strings"
"testing"
)
func TestColorLogS(t *testing.T) {
if runtime.GOOS != "windows" {
// Trace + path.
cls := ColorLogS("[TRAC] Trace level test with path( %s )", "/path/to/somethere")
clsR := fmt.Sprintf(
"[\033[%dmTRAC%s] Trace level test with path(\033[%dm%s%s)",
Blue, EndColor, Yellow, "/path/to/somethere", EndColor)
if cls != clsR {
t.Errorf("ColorLogS:\n Expect => %s\n Got => %s\n", clsR, cls)
}
// Error + error.
cls = ColorLogS("[ERRO] Error level test with error[ %s ]", "test error")
clsR = fmt.Sprintf(
"[\033[%dmERRO%s] Error level test with error[\033[%dm%s%s]",
Red, EndColor, Red, "test error", EndColor)
if cls != clsR {
t.Errorf("ColorLogS:\n Expect => %s\n Got => %s\n", clsR, cls)
}
// Warning + highlight.
cls = ColorLogS("[WARN] Warnning level test with highlight # %s #", "special offer!")
clsR = fmt.Sprintf(
"[\033[%dmWARN%s] Warnning level test with highlight \033[%dm%s%s",
Magenta, EndColor, Gray, "special offer!", EndColor)
if cls != clsR {
t.Errorf("ColorLogS:\n Expect => %s\n Got => %s\n", clsR, cls)
}
// Success.
cls = ColorLogS("[SUCC] Success level test")
clsR = fmt.Sprintf(
"[\033[%dmSUCC%s] Success level test",
Green, EndColor)
if cls != clsR {
t.Errorf("ColorLogS:\n Expect => %s\n Got => %s\n", clsR, cls)
}
// Default.
cls = ColorLogS("[INFO] Default level test")
clsR = fmt.Sprintf(
"[INFO] Default level test")
if cls != clsR {
t.Errorf("ColorLogS:\n Expect => %s\n Got => %s\n", clsR, cls)
}
} else {
// Trace + path.
cls := ColorLogS("[TRAC] Trace level test with path( %s )", "/path/to/somethere")
clsR := fmt.Sprintf(
"[TRAC] Trace level test with path(%s)",
"/path/to/somethere")
if cls != clsR {
t.Errorf("ColorLogS:\n Expect => %s\n Got => %s\n", clsR, cls)
}
// Error + error.
cls = ColorLogS("[ERRO] Error level test with error[ %s ]", "test error")
clsR = fmt.Sprintf(
"[ERRO] Error level test with error[%s]",
"test error")
if cls != clsR {
t.Errorf("ColorLogS:\n Expect => %s\n Got => %s\n", clsR, cls)
}
// Warning + highlight.
cls = ColorLogS("[WARN] Warnning level test with highlight # %s #", "special offer!")
clsR = fmt.Sprintf(
"[WARN] Warnning level test with highlight %s",
"special offer!")
if cls != clsR {
t.Errorf("ColorLogS:\n Expect => %s\n Got => %s\n", clsR, cls)
}
// Success.
cls = ColorLogS("[SUCC] Success level test")
clsR = fmt.Sprintf(
"[SUCC] Success level test")
if cls != clsR {
t.Errorf("ColorLogS:\n Expect => %s\n Got => %s\n", clsR, cls)
}
// Default.
cls = ColorLogS("[INFO] Default level test")
clsR = fmt.Sprintf(
"[INFO] Default level test")
if cls != clsR {
t.Errorf("ColorLogS:\n Expect => %s\n Got => %s\n", clsR, cls)
}
}
}
func TestExecCmd(t *testing.T) {
stdout, stderr, err := ExecCmd("go", "help", "get")
if err != nil {
t.Errorf("ExecCmd:\n Expect => %v\n Got => %v\n", nil, err)
} else if len(stderr) != 0 {
t.Errorf("ExecCmd:\n Expect => %s\n Got => %s\n", "", stderr)
} else if !strings.HasPrefix(stdout, "usage: go get") {
t.Errorf("ExecCmd:\n Expect => %s\n Got => %s\n", "usage: go get", stdout)
}
}
func BenchmarkColorLogS(b *testing.B) {
log := fmt.Sprintf(
"[WARN] This is a tesing log that should be colored, path( %s ),"+
" highlight # %s #, error [ %s ].",
"path to somewhere", "highlighted content", "tesing error")
for i := 0; i < b.N; i++ {
ColorLogS(log)
}
}
func BenchmarkExecCmd(b *testing.B) {
for i := 0; i < b.N; i++ {
ExecCmd("go", "help", "get")
}
}
+157
View File
@@ -0,0 +1,157 @@
// Copyright 2014 com authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package com
import (
"fmt"
"strconv"
)
// Convert string to specify type.
type StrTo string
func (f StrTo) Exist() bool {
return string(f) != string(0x1E)
}
func (f StrTo) Uint8() (uint8, error) {
v, err := strconv.ParseUint(f.String(), 10, 8)
return uint8(v), err
}
func (f StrTo) Int() (int, error) {
v, err := strconv.ParseInt(f.String(), 10, 32)
return int(v), err
}
func (f StrTo) Int64() (int64, error) {
v, err := strconv.ParseInt(f.String(), 10, 64)
return int64(v), err
}
func (f StrTo) MustUint8() uint8 {
v, _ := f.Uint8()
return v
}
func (f StrTo) MustInt() int {
v, _ := f.Int()
return v
}
func (f StrTo) MustInt64() int64 {
v, _ := f.Int64()
return v
}
func (f StrTo) String() string {
if f.Exist() {
return string(f)
}
return ""
}
// Convert any type to string.
func ToStr(value interface{}, args ...int) (s string) {
switch v := value.(type) {
case bool:
s = strconv.FormatBool(v)
case float32:
s = strconv.FormatFloat(float64(v), 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 32))
case float64:
s = strconv.FormatFloat(v, 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 64))
case int:
s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
case int8:
s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
case int16:
s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
case int32:
s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
case int64:
s = strconv.FormatInt(v, argInt(args).Get(0, 10))
case uint:
s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
case uint8:
s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
case uint16:
s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
case uint32:
s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
case uint64:
s = strconv.FormatUint(v, argInt(args).Get(0, 10))
case string:
s = v
case []byte:
s = string(v)
default:
s = fmt.Sprintf("%v", v)
}
return s
}
type argInt []int
func (a argInt) Get(i int, args ...int) (r int) {
if i >= 0 && i < len(a) {
r = a[i]
} else if len(args) > 0 {
r = args[0]
}
return
}
// HexStr2int converts hex format string to decimal number.
func HexStr2int(hexStr string) (int, error) {
num := 0
length := len(hexStr)
for i := 0; i < length; i++ {
char := hexStr[length-i-1]
factor := -1
switch {
case char >= '0' && char <= '9':
factor = int(char) - '0'
case char >= 'a' && char <= 'f':
factor = int(char) - 'a' + 10
default:
return -1, fmt.Errorf("invalid hex: %s", string(char))
}
num += factor * PowInt(16, i)
}
return num, nil
}
// Int2HexStr converts decimal number to hex format string.
func Int2HexStr(num int) (hex string) {
if num == 0 {
return "0"
}
for num > 0 {
r := num % 16
c := "?"
if r >= 0 && r <= 9 {
c = string(r + '0')
} else {
c = string(r + 'a' - 10)
}
hex = c + hex
num = num / 16
}
return hex
}
+56
View File
@@ -0,0 +1,56 @@
// Copyright 2014 com authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package com
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestHexStr2int(t *testing.T) {
Convey("Convert hex format string to decimal", t, func() {
hexDecs := map[string]int{
"1": 1,
"002": 2,
"011": 17,
"0a1": 161,
"35e": 862,
}
for hex, dec := range hexDecs {
val, err := HexStr2int(hex)
So(err, ShouldBeNil)
So(val, ShouldEqual, dec)
}
})
}
func TestInt2HexStr(t *testing.T) {
Convey("Convert decimal to hex format string", t, func() {
decHexs := map[int]string{
1: "1",
2: "2",
17: "11",
161: "a1",
862: "35e",
}
for dec, hex := range decHexs {
val := Int2HexStr(dec)
So(val, ShouldEqual, hex)
}
})
}
+173
View File
@@ -0,0 +1,173 @@
// Copyright 2013 com authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package com
import (
"errors"
"fmt"
"os"
"path"
"strings"
)
// IsDir returns true if given path is a directory,
// or returns false when it's a file or does not exist.
func IsDir(dir string) bool {
f, e := os.Stat(dir)
if e != nil {
return false
}
return f.IsDir()
}
func statDir(dirPath, recPath string, includeDir, isDirOnly bool) ([]string, error) {
dir, err := os.Open(dirPath)
if err != nil {
return nil, err
}
defer dir.Close()
fis, err := dir.Readdir(0)
if err != nil {
return nil, err
}
statList := make([]string, 0)
for _, fi := range fis {
if strings.Contains(fi.Name(), ".DS_Store") {
continue
}
relPath := path.Join(recPath, fi.Name())
curPath := path.Join(dirPath, fi.Name())
if fi.IsDir() {
if includeDir {
statList = append(statList, relPath+"/")
}
s, err := statDir(curPath, relPath, includeDir, isDirOnly)
if err != nil {
return nil, err
}
statList = append(statList, s...)
} else if !isDirOnly {
statList = append(statList, relPath)
}
}
return statList, nil
}
// StatDir gathers information of given directory by depth-first.
// It returns slice of file list and includes subdirectories if enabled;
// it returns error and nil slice when error occurs in underlying functions,
// or given path is not a directory or does not exist.
//
// Slice does not include given path itself.
// If subdirectories is enabled, they will have suffix '/'.
func StatDir(rootPath string, includeDir ...bool) ([]string, error) {
if !IsDir(rootPath) {
return nil, errors.New("not a directory or does not exist: " + rootPath)
}
isIncludeDir := false
if len(includeDir) >= 1 {
isIncludeDir = includeDir[0]
}
return statDir(rootPath, "", isIncludeDir, false)
}
// GetAllSubDirs returns all subdirectories of given root path.
// Slice does not include given path itself.
func GetAllSubDirs(rootPath string) ([]string, error) {
if !IsDir(rootPath) {
return nil, errors.New("not a directory or does not exist: " + rootPath)
}
return statDir(rootPath, "", true, true)
}
// GetFileListBySuffix returns an ordered list of file paths.
// It recognize if given path is a file, and don't do recursive find.
func GetFileListBySuffix(dirPath, suffix string) ([]string, error) {
if !IsExist(dirPath) {
return nil, fmt.Errorf("given path does not exist: %s", dirPath)
} else if IsFile(dirPath) {
return []string{dirPath}, nil
}
// Given path is a directory.
dir, err := os.Open(dirPath)
if err != nil {
return nil, err
}
fis, err := dir.Readdir(0)
if err != nil {
return nil, err
}
files := make([]string, 0, len(fis))
for _, fi := range fis {
if strings.HasSuffix(fi.Name(), suffix) {
files = append(files, path.Join(dirPath, fi.Name()))
}
}
return files, nil
}
// CopyDir copy files recursively from source to target directory.
//
// The filter accepts a function that process the path info.
// and should return true for need to filter.
//
// It returns error when error occurs in underlying functions.
func CopyDir(srcPath, destPath string, filters ...func(filePath string) bool) error {
// Check if target directory exists.
if IsExist(destPath) {
return errors.New("file or directory alreay exists: " + destPath)
}
err := os.MkdirAll(destPath, os.ModePerm)
if err != nil {
return err
}
// Gather directory info.
infos, err := StatDir(srcPath, true)
if err != nil {
return err
}
var filter func(filePath string) bool
if len(filters) > 0 {
filter = filters[0]
}
for _, info := range infos {
if filter != nil && filter(info) {
continue
}
curPath := path.Join(destPath, info)
if strings.HasSuffix(info, "/") {
err = os.MkdirAll(curPath, os.ModePerm)
} else {
err = Copy(path.Join(srcPath, info), curPath)
}
if err != nil {
return err
}
}
return nil
}
+58
View File
@@ -0,0 +1,58 @@
// Copyright 2013 com authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package com
import (
"os"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestIsDir(t *testing.T) {
Convey("Check if given path is a directory", t, func() {
Convey("Pass a file name", func() {
So(IsDir("file.go"), ShouldEqual, false)
})
Convey("Pass a directory name", func() {
So(IsDir("testdata"), ShouldEqual, true)
})
Convey("Pass a invalid path", func() {
So(IsDir("foo"), ShouldEqual, false)
})
})
}
func TestCopyDir(t *testing.T) {
Convey("Items of two slices should be same", t, func() {
s1, err := StatDir("testdata", true)
So(err, ShouldEqual, nil)
err = CopyDir("testdata", "testdata2")
So(err, ShouldEqual, nil)
s2, err := StatDir("testdata2", true)
os.RemoveAll("testdata2")
So(err, ShouldEqual, nil)
So(CompareSliceStr(s1, s2), ShouldEqual, true)
})
}
func BenchmarkIsDir(b *testing.B) {
for i := 0; i < b.N; i++ {
IsDir("file.go")
}
}
+299
View File
@@ -0,0 +1,299 @@
// Copyright 2013 com authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package com_test
import (
"fmt"
"io/ioutil"
"net/http"
"github.com/Unknwon/com"
)
// ------------------------------
// cmd.go
// ------------------------------
func ExampleColorLogS() {
coloredLog := com.ColorLogS(fmt.Sprintf(
"[WARN] This is a tesing log that should be colored, path( %s ),"+
" highlight # %s #, error [ %s ].",
"path to somewhere", "highlighted content", "tesing error"))
fmt.Println(coloredLog)
}
func ExampleColorLog() {
com.ColorLog(fmt.Sprintf(
"[WARN] This is a tesing log that should be colored, path( %s ),"+
" highlight # %s #, error [ %s ].",
"path to somewhere", "highlighted content", "tesing error"))
}
func ExampleExecCmd() {
stdout, stderr, err := com.ExecCmd("go", "help", "get")
fmt.Println(stdout, stderr, err)
}
// ------------- END ------------
// ------------------------------
// html.go
// ------------------------------
func ExampleHtml2JS() {
htm := "<div id=\"button\" class=\"btn\">Click me</div>\n\r"
js := string(com.Html2JS([]byte(htm)))
fmt.Println(js)
// Output: <div id=\"button\" class=\"btn\">Click me</div>\n
}
// ------------- END ------------
// ------------------------------
// path.go
// ------------------------------
func ExampleGetGOPATHs() {
gps := com.GetGOPATHs()
fmt.Println(gps)
}
func ExampleGetSrcPath() {
srcPath, err := com.GetSrcPath("github.com/Unknwon/com")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(srcPath)
}
func ExampleHomeDir() {
hd, err := com.HomeDir()
fmt.Println(hd, err)
}
// ------------- END ------------
// ------------------------------
// file.go
// ------------------------------
func ExampleIsFile() {
if com.IsFile("file.go") {
fmt.Println("file.go exists")
return
}
fmt.Println("file.go is not a file or does not exist")
}
func ExampleIsExist() {
if com.IsExist("file.go") {
fmt.Println("file.go exists")
return
}
fmt.Println("file.go does not exist")
}
// ------------- END ------------
// ------------------------------
// dir.go
// ------------------------------
func ExampleIsDir() {
if com.IsDir("files") {
fmt.Println("directory 'files' exists")
return
}
fmt.Println("'files' is not a directory or does not exist")
}
// ------------- END ------------
// ------------------------------
// string.go
// ------------------------------
func ExampleIsLetter() {
fmt.Println(com.IsLetter('1'))
fmt.Println(com.IsLetter('['))
fmt.Println(com.IsLetter('a'))
fmt.Println(com.IsLetter('Z'))
// Output:
// false
// false
// true
// true
}
func ExampleExpand() {
match := map[string]string{
"domain": "gowalker.org",
"subdomain": "github.com",
}
s := "http://{domain}/{subdomain}/{0}/{1}"
fmt.Println(com.Expand(s, match, "Unknwon", "gowalker"))
// Output: http://gowalker.org/github.com/Unknwon/gowalker
}
// ------------- END ------------
// ------------------------------
// http.go
// ------------------------------
func ExampleHttpGet() ([]byte, error) {
rc, err := com.HttpGet(&http.Client{}, "http://gowalker.org", nil)
if err != nil {
return nil, err
}
p, err := ioutil.ReadAll(rc)
rc.Close()
return p, err
}
func ExampleHttpGetBytes() ([]byte, error) {
p, err := com.HttpGetBytes(&http.Client{}, "http://gowalker.org", nil)
return p, err
}
func ExampleHttpGetJSON() interface{} {
j := com.HttpGetJSON(&http.Client{}, "http://gowalker.org", nil)
return j
}
type rawFile struct {
name string
rawURL string
data []byte
}
func (rf *rawFile) Name() string {
return rf.name
}
func (rf *rawFile) RawUrl() string {
return rf.rawURL
}
func (rf *rawFile) Data() []byte {
return rf.data
}
func (rf *rawFile) SetData(p []byte) {
rf.data = p
}
func ExampleFetchFiles() {
// Code that should be outside of your function body.
// type rawFile struct {
// name string
// rawURL string
// data []byte
// }
// func (rf *rawFile) Name() string {
// return rf.name
// }
// func (rf *rawFile) RawUrl() string {
// return rf.rawURL
// }
// func (rf *rawFile) Data() []byte {
// return rf.data
// }
// func (rf *rawFile) SetData(p []byte) {
// rf.data = p
// }
files := []com.RawFile{
&rawFile{rawURL: "http://example.com"},
&rawFile{rawURL: "http://example.com/foo"},
}
err := com.FetchFiles(&http.Client{}, files, nil)
fmt.Println(err, len(files[0].Data()), len(files[1].Data()))
}
func ExampleFetchFilesCurl() {
// Code that should be outside of your function body.
// type rawFile struct {
// name string
// rawURL string
// data []byte
// }
// func (rf *rawFile) Name() string {
// return rf.name
// }
// func (rf *rawFile) RawUrl() string {
// return rf.rawURL
// }
// func (rf *rawFile) Data() []byte {
// return rf.data
// }
// func (rf *rawFile) SetData(p []byte) {
// rf.data = p
// }
files := []com.RawFile{
&rawFile{rawURL: "http://example.com"},
&rawFile{rawURL: "http://example.com/foo"},
}
err := com.FetchFilesCurl(files)
fmt.Println(err, len(files[0].Data()), len(files[1].Data()))
}
// ------------- END ------------
// ------------------------------
// regex.go
// ------------------------------
func ExampleIsEmail() {
fmt.Println(com.IsEmail("test@example.com"))
fmt.Println(com.IsEmail("@example.com"))
// Output:
// true
// false
}
func ExampleIsUrl() {
fmt.Println(com.IsUrl("http://example.com"))
fmt.Println(com.IsUrl("http//example.com"))
// Output:
// true
// false
}
// ------------- END ------------
// ------------------------------
// slice.go
// ------------------------------
func ExampleAppendStr() {
s := []string{"a"}
s = com.AppendStr(s, "a")
s = com.AppendStr(s, "b")
fmt.Println(s)
// Output: [a b]
}
// ------------- END ------------
+145
View File
@@ -0,0 +1,145 @@
// Copyright 2013 com authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package com
import (
"fmt"
"io"
"io/ioutil"
"math"
"os"
"path"
)
// Storage unit constants.
const (
Byte = 1
KByte = Byte * 1024
MByte = KByte * 1024
GByte = MByte * 1024
TByte = GByte * 1024
PByte = TByte * 1024
EByte = PByte * 1024
)
func logn(n, b float64) float64 {
return math.Log(n) / math.Log(b)
}
func humanateBytes(s uint64, base float64, sizes []string) string {
if s < 10 {
return fmt.Sprintf("%dB", s)
}
e := math.Floor(logn(float64(s), base))
suffix := sizes[int(e)]
val := float64(s) / math.Pow(base, math.Floor(e))
f := "%.0f"
if val < 10 {
f = "%.1f"
}
return fmt.Sprintf(f+"%s", val, suffix)
}
// HumaneFileSize calculates the file size and generate user-friendly string.
func HumaneFileSize(s uint64) string {
sizes := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"}
return humanateBytes(s, 1024, sizes)
}
// FileMTime returns file modified time and possible error.
func FileMTime(file string) (int64, error) {
f, err := os.Stat(file)
if err != nil {
return 0, err
}
return f.ModTime().Unix(), nil
}
// FileSize returns file size in bytes and possible error.
func FileSize(file string) (int64, error) {
f, err := os.Stat(file)
if err != nil {
return 0, err
}
return f.Size(), nil
}
// Copy copies file from source to target path.
func Copy(src, dest string) error {
// Gather file information to set back later.
si, err := os.Lstat(src)
if err != nil {
return err
}
// Handle symbolic link.
if si.Mode()&os.ModeSymlink != 0 {
target, err := os.Readlink(src)
if err != nil {
return err
}
// NOTE: os.Chmod and os.Chtimes don't recoganize symbolic link,
// which will lead "no such file or directory" error.
return os.Symlink(target, dest)
}
sr, err := os.Open(src)
if err != nil {
return err
}
defer sr.Close()
dw, err := os.Create(dest)
if err != nil {
return err
}
defer dw.Close()
if _, err = io.Copy(dw, sr); err != nil {
return err
}
// Set back file information.
if err = os.Chtimes(dest, si.ModTime(), si.ModTime()); err != nil {
return err
}
return os.Chmod(dest, si.Mode())
}
// WriteFile writes data to a file named by filename.
// If the file does not exist, WriteFile creates it
// and its upper level paths.
func WriteFile(filename string, data []byte) error {
os.MkdirAll(path.Dir(filename), os.ModePerm)
return ioutil.WriteFile(filename, data, 0655)
}
// IsFile returns true if given path is a file,
// or returns false when it's a directory or does not exist.
func IsFile(filePath string) bool {
f, e := os.Stat(filePath)
if e != nil {
return false
}
return !f.IsDir()
}
// IsExist checks whether a file or directory exists.
// It returns false when the file or directory does not exist.
func IsExist(path string) bool {
_, err := os.Stat(path)
return err == nil || os.IsExist(err)
}
+61
View File
@@ -0,0 +1,61 @@
// Copyright 2013 com authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package com
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestIsFile(t *testing.T) {
if !IsFile("file.go") {
t.Errorf("IsExist:\n Expect => %v\n Got => %v\n", true, false)
}
if IsFile("testdata") {
t.Errorf("IsExist:\n Expect => %v\n Got => %v\n", false, true)
}
if IsFile("files.go") {
t.Errorf("IsExist:\n Expect => %v\n Got => %v\n", false, true)
}
}
func TestIsExist(t *testing.T) {
Convey("Check if file or directory exists", t, func() {
Convey("Pass a file name that exists", func() {
So(IsExist("file.go"), ShouldEqual, true)
})
Convey("Pass a directory name that exists", func() {
So(IsExist("testdata"), ShouldEqual, true)
})
Convey("Pass a directory name that does not exist", func() {
So(IsExist(".hg"), ShouldEqual, false)
})
})
}
func BenchmarkIsFile(b *testing.B) {
for i := 0; i < b.N; i++ {
IsFile("file.go")
}
}
func BenchmarkIsExist(b *testing.B) {
for i := 0; i < b.N; i++ {
IsExist("file.go")
}
}
+60
View File
@@ -0,0 +1,60 @@
// Copyright 2013 com authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package com
import (
"html"
"regexp"
"strings"
)
// Html2JS converts []byte type of HTML content into JS format.
func Html2JS(data []byte) []byte {
s := string(data)
s = strings.Replace(s, `\`, `\\`, -1)
s = strings.Replace(s, "\n", `\n`, -1)
s = strings.Replace(s, "\r", "", -1)
s = strings.Replace(s, "\"", `\"`, -1)
s = strings.Replace(s, "<table>", "&lt;table>", -1)
return []byte(s)
}
// encode html chars to string
func HtmlEncode(str string) string {
return html.EscapeString(str)
}
// decode string to html chars
func HtmlDecode(str string) string {
return html.UnescapeString(str)
}
// strip tags in html string
func StripTags(src string) string {
//去除style,script,html tag
re := regexp.MustCompile(`(?s)<(?:style|script)[^<>]*>.*?</(?:style|script)>|</?[a-z][a-z0-9]*[^<>]*>|<!--.*?-->`)
src = re.ReplaceAllString(src, "")
//trim all spaces(2+) into \n
re = regexp.MustCompile(`\s{2,}`)
src = re.ReplaceAllString(src, "\n")
return strings.TrimSpace(src)
}
// change \n to <br/>
func Nl2br(str string) string {
return strings.Replace(str, "\n", "<br/>", -1)
}
+35
View File
@@ -0,0 +1,35 @@
// Copyright 2013 com authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package com
import (
"testing"
)
func TestHtml2JS(t *testing.T) {
htm := "<div id=\"button\" class=\"btn\">Click me</div>\n\r"
js := string(Html2JS([]byte(htm)))
jsR := `<div id=\"button\" class=\"btn\">Click me</div>\n`
if js != jsR {
t.Errorf("Html2JS:\n Expect => %s\n Got => %s\n", jsR, js)
}
}
func BenchmarkHtml2JS(b *testing.B) {
htm := "<div id=\"button\" class=\"btn\">Click me</div>\n\r"
for i := 0; i < b.N; i++ {
Html2JS([]byte(htm))
}
}
+201
View File
@@ -0,0 +1,201 @@
// Copyright 2013 com authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package com
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path"
)
type NotFoundError struct {
Message string
}
func (e NotFoundError) Error() string {
return e.Message
}
type RemoteError struct {
Host string
Err error
}
func (e *RemoteError) Error() string {
return e.Err.Error()
}
var UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1541.0 Safari/537.36"
// HttpCall makes HTTP method call.
func HttpCall(client *http.Client, method, url string, header http.Header, body io.Reader) (io.ReadCloser, error) {
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", UserAgent)
for k, vs := range header {
req.Header[k] = vs
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode == 200 {
return resp.Body, nil
}
resp.Body.Close()
if resp.StatusCode == 404 { // 403 can be rate limit error. || resp.StatusCode == 403 {
err = fmt.Errorf("resource not found: %s", url)
} else {
err = fmt.Errorf("%s %s -> %d", method, url, resp.StatusCode)
}
return nil, err
}
// HttpGet gets the specified resource.
// ErrNotFound is returned if the server responds with status 404.
func HttpGet(client *http.Client, url string, header http.Header) (io.ReadCloser, error) {
return HttpCall(client, "GET", url, header, nil)
}
// HttpPost posts the specified resource.
// ErrNotFound is returned if the server responds with status 404.
func HttpPost(client *http.Client, url string, header http.Header, body []byte) (io.ReadCloser, error) {
return HttpCall(client, "POST", url, header, bytes.NewBuffer(body))
}
// HttpGetToFile gets the specified resource and writes to file.
// ErrNotFound is returned if the server responds with status 404.
func HttpGetToFile(client *http.Client, url string, header http.Header, fileName string) error {
rc, err := HttpGet(client, url, header)
if err != nil {
return err
}
defer rc.Close()
os.MkdirAll(path.Dir(fileName), os.ModePerm)
f, err := os.Create(fileName)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, rc)
return err
}
// HttpGetBytes gets the specified resource. ErrNotFound is returned if the server
// responds with status 404.
func HttpGetBytes(client *http.Client, url string, header http.Header) ([]byte, error) {
rc, err := HttpGet(client, url, header)
if err != nil {
return nil, err
}
defer rc.Close()
return ioutil.ReadAll(rc)
}
// HttpGetJSON gets the specified resource and mapping to struct.
// ErrNotFound is returned if the server responds with status 404.
func HttpGetJSON(client *http.Client, url string, v interface{}) error {
rc, err := HttpGet(client, url, nil)
if err != nil {
return err
}
defer rc.Close()
err = json.NewDecoder(rc).Decode(v)
if _, ok := err.(*json.SyntaxError); ok {
return fmt.Errorf("JSON syntax error at %s", url)
}
return nil
}
// HttpPostJSON posts the specified resource with struct values,
// and maps results to struct.
// ErrNotFound is returned if the server responds with status 404.
func HttpPostJSON(client *http.Client, url string, body, v interface{}) error {
data, err := json.Marshal(body)
if err != nil {
return err
}
rc, err := HttpPost(client, url, http.Header{"content-type": []string{"application/json"}}, data)
if err != nil {
return err
}
defer rc.Close()
err = json.NewDecoder(rc).Decode(v)
if _, ok := err.(*json.SyntaxError); ok {
return fmt.Errorf("JSON syntax error at %s", url)
}
return nil
}
// A RawFile describes a file that can be downloaded.
type RawFile interface {
Name() string
RawUrl() string
Data() []byte
SetData([]byte)
}
// FetchFiles fetches files specified by the rawURL field in parallel.
func FetchFiles(client *http.Client, files []RawFile, header http.Header) error {
ch := make(chan error, len(files))
for i := range files {
go func(i int) {
p, err := HttpGetBytes(client, files[i].RawUrl(), nil)
if err != nil {
ch <- err
return
}
files[i].SetData(p)
ch <- nil
}(i)
}
for _ = range files {
if err := <-ch; err != nil {
return err
}
}
return nil
}
// FetchFiles uses command `curl` to fetch files specified by the rawURL field in parallel.
func FetchFilesCurl(files []RawFile, curlOptions ...string) error {
ch := make(chan error, len(files))
for i := range files {
go func(i int) {
stdout, _, err := ExecCmd("curl", append(curlOptions, files[i].RawUrl())...)
if err != nil {
ch <- err
return
}
files[i].SetData([]byte(stdout))
ch <- nil
}(i)
}
for _ = range files {
if err := <-ch; err != nil {
return err
}
}
return nil
}
+111
View File
@@ -0,0 +1,111 @@
// Copyright 2013 com authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package com
import (
"io/ioutil"
"net/http"
"strings"
"testing"
)
var examplePrefix = `<!doctype html>
<html>
<head>
<title>Example Domain</title>
`
func TestHttpGet(t *testing.T) {
// 200.
rc, err := HttpGet(&http.Client{}, "http://example.com", nil)
if err != nil {
t.Fatalf("HttpGet:\n Expect => %v\n Got => %s\n", nil, err)
}
p, err := ioutil.ReadAll(rc)
if err != nil {
t.Errorf("HttpGet:\n Expect => %v\n Got => %s\n", nil, err)
}
s := string(p)
if !strings.HasPrefix(s, examplePrefix) {
t.Errorf("HttpGet:\n Expect => %s\n Got => %s\n", examplePrefix, s)
}
}
func TestHttpGetBytes(t *testing.T) {
p, err := HttpGetBytes(&http.Client{}, "http://example.com", nil)
if err != nil {
t.Errorf("HttpGetBytes:\n Expect => %v\n Got => %s\n", nil, err)
}
s := string(p)
if !strings.HasPrefix(s, examplePrefix) {
t.Errorf("HttpGet:\n Expect => %s\n Got => %s\n", examplePrefix, s)
}
}
func TestHttpGetJSON(t *testing.T) {
}
type rawFile struct {
name string
rawURL string
data []byte
}
func (rf *rawFile) Name() string {
return rf.name
}
func (rf *rawFile) RawUrl() string {
return rf.rawURL
}
func (rf *rawFile) Data() []byte {
return rf.data
}
func (rf *rawFile) SetData(p []byte) {
rf.data = p
}
func TestFetchFiles(t *testing.T) {
files := []RawFile{
&rawFile{rawURL: "http://example.com"},
&rawFile{rawURL: "http://example.com"},
}
err := FetchFiles(&http.Client{}, files, nil)
if err != nil {
t.Errorf("FetchFiles:\n Expect => %v\n Got => %s\n", nil, err)
} else if len(files[0].Data()) != 1270 {
t.Errorf("FetchFiles:\n Expect => %d\n Got => %d\n", 1270, len(files[0].Data()))
} else if len(files[1].Data()) != 1270 {
t.Errorf("FetchFiles:\n Expect => %d\n Got => %d\n", 1270, len(files[1].Data()))
}
}
func TestFetchFilesCurl(t *testing.T) {
files := []RawFile{
&rawFile{rawURL: "http://example.com"},
&rawFile{rawURL: "http://example.com"},
}
err := FetchFilesCurl(files)
if err != nil {
t.Errorf("FetchFilesCurl:\n Expect => %v\n Got => %s\n", nil, err)
} else if len(files[0].Data()) != 1270 {
t.Errorf("FetchFilesCurl:\n Expect => %d\n Got => %d\n", 1270, len(files[0].Data()))
} else if len(files[1].Data()) != 1270 {
t.Errorf("FetchFilesCurl:\n Expect => %d\n Got => %d\n", 1270, len(files[1].Data()))
}
}
+24
View File
@@ -0,0 +1,24 @@
// Copyright 2014 com authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package com
// PowInt is int type of math.Pow function.
func PowInt(x int, y int) int {
num := 1
for i := 0; i < y; i++ {
num *= x
}
return num
}
+80
View File
@@ -0,0 +1,80 @@
// Copyright 2013 com authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package com
import (
"errors"
"os"
"path/filepath"
"runtime"
"strings"
)
// GetGOPATHs returns all paths in GOPATH variable.
func GetGOPATHs() []string {
gopath := os.Getenv("GOPATH")
var paths []string
if runtime.GOOS == "windows" {
gopath = strings.Replace(gopath, "\\", "/", -1)
paths = strings.Split(gopath, ";")
} else {
paths = strings.Split(gopath, ":")
}
return paths
}
// GetSrcPath returns app. source code path.
// It only works when you have src. folder in GOPATH,
// it returns error not able to locate source folder path.
func GetSrcPath(importPath string) (appPath string, err error) {
paths := GetGOPATHs()
for _, p := range paths {
if IsExist(p + "/src/" + importPath + "/") {
appPath = p + "/src/" + importPath + "/"
break
}
}
if len(appPath) == 0 {
return "", errors.New("Unable to locate source folder path")
}
appPath = filepath.Dir(appPath) + "/"
if runtime.GOOS == "windows" {
// Replace all '\' to '/'.
appPath = strings.Replace(appPath, "\\", "/", -1)
}
return appPath, nil
}
// HomeDir returns path of '~'(in Linux) on Windows,
// it returns error when the variable does not exist.
func HomeDir() (home string, err error) {
if runtime.GOOS == "windows" {
home = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
if home == "" {
home = os.Getenv("USERPROFILE")
}
} else {
home = os.Getenv("HOME")
}
if len(home) == 0 {
return "", errors.New("Cannot specify home directory because it's empty")
}
return home, nil
}
+67
View File
@@ -0,0 +1,67 @@
// Copyright 2013 com authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package com
import (
"os"
"runtime"
"testing"
)
func TestGetGOPATHs(t *testing.T) {
var gpsR []string
if runtime.GOOS != "windows" {
gpsR = []string{"path/to/gopath1", "path/to/gopath2", "path/to/gopath3"}
os.Setenv("GOPATH", "path/to/gopath1:path/to/gopath2:path/to/gopath3")
} else {
gpsR = []string{"path/to/gopath1", "path/to/gopath2", "path/to/gopath3"}
os.Setenv("GOPATH", "path\\to\\gopath1;path\\to\\gopath2;path\\to\\gopath3")
}
gps := GetGOPATHs()
if !CompareSliceStr(gps, gpsR) {
t.Errorf("GetGOPATHs:\n Expect => %s\n Got => %s\n", gpsR, gps)
}
}
func TestGetSrcPath(t *testing.T) {
}
func TestHomeDir(t *testing.T) {
_, err := HomeDir()
if err != nil {
t.Errorf("HomeDir:\n Expect => %v\n Got => %s\n", nil, err)
}
}
func BenchmarkGetGOPATHs(b *testing.B) {
for i := 0; i < b.N; i++ {
GetGOPATHs()
}
}
func BenchmarkGetSrcPath(b *testing.B) {
for i := 0; i < b.N; i++ {
GetSrcPath("github.com/Unknwon/com")
}
}
func BenchmarkHomeDir(b *testing.B) {
for i := 0; i < b.N; i++ {
HomeDir()
}
}
+56
View File
@@ -0,0 +1,56 @@
// Copyright 2013 com authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package com
import "regexp"
const (
regex_email_pattern = `(?i)[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}`
regex_strict_email_pattern = `(?i)[A-Z0-9!#$%&'*+/=?^_{|}~-]+` +
`(?:\.[A-Z0-9!#$%&'*+/=?^_{|}~-]+)*` +
`@(?:[A-Z0-9](?:[A-Z0-9-]*[A-Z0-9])?\.)+` +
`[A-Z0-9](?:[A-Z0-9-]*[A-Z0-9])?`
regex_url_pattern = `(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?`
)
var (
regex_email *regexp.Regexp
regex_strict_email *regexp.Regexp
regex_url *regexp.Regexp
)
func init() {
regex_email = regexp.MustCompile(regex_email_pattern)
regex_strict_email = regexp.MustCompile(regex_strict_email_pattern)
regex_url = regexp.MustCompile(regex_url_pattern)
}
// validate string is an email address, if not return false
// basically validation can match 99% cases
func IsEmail(email string) bool {
return regex_email.MatchString(email)
}
// validate string is an email address, if not return false
// this validation omits RFC 2822
func IsEmailRFC(email string) bool {
return regex_strict_email.MatchString(email)
}
// validate string is a url link, if not return false
// simple validation can match 99% cases
func IsUrl(url string) bool {
return regex_url.MatchString(url)
}
+70
View File
@@ -0,0 +1,70 @@
// Copyright 2013 com authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package com
import (
"testing"
)
func TestIsEmail(t *testing.T) {
emails := map[string]bool{
`test@example.com`: true,
`single-character@b.org`: true,
`uncommon_address@test.museum`: true,
`local@sld.UPPER`: true,
`@missing.org`: false,
`missing@.com`: false,
`missing@qq.`: false,
`wrong-ip@127.1.1.1.26`: false,
}
for e, r := range emails {
b := IsEmail(e)
if b != r {
t.Errorf("IsEmail:\n Expect => %v\n Got => %v\n", r, b)
}
}
}
func TestIsUrl(t *testing.T) {
urls := map[string]bool{
"http://www.example.com": true,
"http://example.com": true,
"http://example.com?user=test&password=test": true,
"http://example.com?user=test#login": true,
"ftp://example.com": true,
"https://example.com": true,
"htp://example.com": false,
"http//example.com": false,
"http://example": true,
}
for u, r := range urls {
b := IsUrl(u)
if b != r {
t.Errorf("IsUrl:\n Expect => %v\n Got => %v\n", r, b)
}
}
}
func BenchmarkIsEmail(b *testing.B) {
for i := 0; i < b.N; i++ {
IsEmail("test@example.com")
}
}
func BenchmarkIsUrl(b *testing.B) {
for i := 0; i < b.N; i++ {
IsEmail("http://example.com")
}
}
+87
View File
@@ -0,0 +1,87 @@
// Copyright 2013 com authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package com
import (
"strings"
)
// AppendStr appends string to slice with no duplicates.
func AppendStr(strs []string, str string) []string {
for _, s := range strs {
if s == str {
return strs
}
}
return append(strs, str)
}
// CompareSliceStr compares two 'string' type slices.
// It returns true if elements and order are both the same.
func CompareSliceStr(s1, s2 []string) bool {
if len(s1) != len(s2) {
return false
}
for i := range s1 {
if s1[i] != s2[i] {
return false
}
}
return true
}
// CompareSliceStr compares two 'string' type slices.
// It returns true if elements are the same, and ignores the order.
func CompareSliceStrU(s1, s2 []string) bool {
if len(s1) != len(s2) {
return false
}
for i := range s1 {
for j := len(s2) - 1; j >= 0; j-- {
if s1[i] == s2[j] {
s2 = append(s2[:j], s2[j+1:]...)
break
}
}
}
if len(s2) > 0 {
return false
}
return true
}
// IsSliceContainsStr returns true if the string exists in given slice, ignore case.
func IsSliceContainsStr(sl []string, str string) bool {
str = strings.ToLower(str)
for _, s := range sl {
if strings.ToLower(s) == str {
return true
}
}
return false
}
// IsSliceContainsInt64 returns true if the int64 exists in given slice.
func IsSliceContainsInt64(sl []int64, i int64) bool {
for _, s := range sl {
if s == i {
return true
}
}
return false
}
+99
View File
@@ -0,0 +1,99 @@
// Copyright 2013 com authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package com
import (
"fmt"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestAppendStr(t *testing.T) {
Convey("Append a string to a slice with no duplicates", t, func() {
s := []string{"a"}
Convey("Append a string that does not exist in slice", func() {
s = AppendStr(s, "b")
So(len(s), ShouldEqual, 2)
})
Convey("Append a string that does exist in slice", func() {
s = AppendStr(s, "b")
So(len(s), ShouldEqual, 2)
})
})
}
func TestCompareSliceStr(t *testing.T) {
Convey("Compares two 'string' type slices with elements and order", t, func() {
Convey("Compare two slices that do have same elements and order", func() {
So(CompareSliceStr(
[]string{"1", "2", "3"}, []string{"1", "2", "3"}), ShouldBeTrue)
})
Convey("Compare two slices that do have same elements but does not have same order", func() {
So(!CompareSliceStr(
[]string{"2", "1", "3"}, []string{"1", "2", "3"}), ShouldBeTrue)
})
Convey("Compare two slices that have different number of elements", func() {
So(!CompareSliceStr(
[]string{"2", "1"}, []string{"1", "2", "3"}), ShouldBeTrue)
})
})
}
func TestCompareSliceStrU(t *testing.T) {
Convey("Compare two 'string' type slices with elements and ignore the order", t, func() {
Convey("Compare two slices that do have same elements and order", func() {
So(CompareSliceStrU(
[]string{"1", "2", "3"}, []string{"1", "2", "3"}), ShouldBeTrue)
})
Convey("Compare two slices that do have same elements but does not have same order", func() {
So(CompareSliceStrU(
[]string{"2", "1", "3"}, []string{"1", "2", "3"}), ShouldBeTrue)
})
Convey("Compare two slices that have different number of elements", func() {
So(!CompareSliceStrU(
[]string{"2", "1"}, []string{"1", "2", "3"}), ShouldBeTrue)
})
})
}
func BenchmarkAppendStr(b *testing.B) {
s := []string{"a"}
for i := 0; i < b.N; i++ {
s = AppendStr(s, fmt.Sprint(b.N%3))
}
}
func BenchmarkCompareSliceStr(b *testing.B) {
s1 := []string{"1", "2", "3"}
s2 := []string{"1", "2", "3"}
for i := 0; i < b.N; i++ {
CompareSliceStr(s1, s2)
}
}
func BenchmarkCompareSliceStrU(b *testing.B) {
s1 := []string{"1", "4", "2", "3"}
s2 := []string{"1", "2", "3", "4"}
for i := 0; i < b.N; i++ {
CompareSliceStrU(s1, s2)
}
}
+140
View File
@@ -0,0 +1,140 @@
// Copyright 2013 com authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package com
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"errors"
"io"
r "math/rand"
"strconv"
"strings"
"time"
)
// AESEncrypt encrypts text and given key with AES.
func AESEncrypt(key, text []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
b := base64.StdEncoding.EncodeToString(text)
ciphertext := make([]byte, aes.BlockSize+len(b))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
cfb := cipher.NewCFBEncrypter(block, iv)
cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(b))
return ciphertext, nil
}
// AESDecrypt decrypts text and given key with AES.
func AESDecrypt(key, text []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
if len(text) < aes.BlockSize {
return nil, errors.New("ciphertext too short")
}
iv := text[:aes.BlockSize]
text = text[aes.BlockSize:]
cfb := cipher.NewCFBDecrypter(block, iv)
cfb.XORKeyStream(text, text)
data, err := base64.StdEncoding.DecodeString(string(text))
if err != nil {
return nil, err
}
return data, nil
}
// IsLetter returns true if the 'l' is an English letter.
func IsLetter(l uint8) bool {
n := (l | 0x20) - 'a'
if n >= 0 && n < 26 {
return true
}
return false
}
// Expand replaces {k} in template with match[k] or subs[atoi(k)] if k is not in match.
func Expand(template string, match map[string]string, subs ...string) string {
var p []byte
var i int
for {
i = strings.Index(template, "{")
if i < 0 {
break
}
p = append(p, template[:i]...)
template = template[i+1:]
i = strings.Index(template, "}")
if s, ok := match[template[:i]]; ok {
p = append(p, s...)
} else {
j, _ := strconv.Atoi(template[:i])
if j >= len(subs) {
p = append(p, []byte("Missing")...)
} else {
p = append(p, subs[j]...)
}
}
template = template[i+1:]
}
p = append(p, template...)
return string(p)
}
// Reverse s string, support unicode
func Reverse(s string) string {
n := len(s)
runes := make([]rune, n)
for _, rune := range s {
n--
runes[n] = rune
}
return string(runes[n:])
}
// RandomCreateBytes generate random []byte by specify chars.
func RandomCreateBytes(n int, alphabets ...byte) []byte {
const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
var bytes = make([]byte, n)
var randby bool
if num, err := rand.Read(bytes); num != n || err != nil {
r.Seed(time.Now().UnixNano())
randby = true
}
for i, b := range bytes {
if len(alphabets) == 0 {
if randby {
bytes[i] = alphanum[r.Intn(len(alphanum))]
} else {
bytes[i] = alphanum[b%byte(len(alphanum))]
}
} else {
if randby {
bytes[i] = alphabets[r.Intn(len(alphabets))]
} else {
bytes[i] = alphabets[b%byte(len(alphabets))]
}
}
}
return bytes
}
+82
View File
@@ -0,0 +1,82 @@
// Copyright 2013 com authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package com
import (
"testing"
)
func TestIsLetter(t *testing.T) {
if IsLetter('1') {
t.Errorf("IsLetter:\n Expect => %v\n Got => %v\n", false, true)
}
if IsLetter('[') {
t.Errorf("IsLetter:\n Expect => %v\n Got => %v\n", false, true)
}
if !IsLetter('a') {
t.Errorf("IsLetter:\n Expect => %v\n Got => %v\n", true, false)
}
if !IsLetter('Z') {
t.Errorf("IsLetter:\n Expect => %v\n Got => %v\n", true, false)
}
}
func TestExpand(t *testing.T) {
match := map[string]string{
"domain": "gowalker.org",
"subdomain": "github.com",
}
s := "http://{domain}/{subdomain}/{0}/{1}"
sR := "http://gowalker.org/github.com/Unknwon/gowalker"
if Expand(s, match, "Unknwon", "gowalker") != sR {
t.Errorf("Expand:\n Expect => %s\n Got => %s\n", sR, s)
}
}
func TestReverse(t *testing.T) {
if Reverse("abcdefg") != "gfedcba" {
t.Errorf("Reverse:\n Except => %s\n Got =>%s\n", "gfedcba", Reverse("abcdefg"))
}
if Reverse("上善若水厚德载物") != "物载德厚水若善上" {
t.Errorf("Reverse:\n Except => %s\n Got =>%s\n", "物载德厚水若善上", Reverse("上善若水厚德载物"))
}
}
func BenchmarkIsLetter(b *testing.B) {
for i := 0; i < b.N; i++ {
IsLetter('a')
}
}
func BenchmarkExpand(b *testing.B) {
match := map[string]string{
"domain": "gowalker.org",
"subdomain": "github.com",
}
s := "http://{domain}/{subdomain}/{0}/{1}"
for i := 0; i < b.N; i++ {
Expand(s, match, "Unknwon", "gowalker")
}
}
func BenchmarkReverse(b *testing.B) {
s := "abscef中文"
for i := 0; i < b.N; i++ {
Reverse(s)
}
}
+1
View File
@@ -0,0 +1 @@
TestSaveFile
+1
View File
@@ -0,0 +1 @@
TestSaveFileS
@@ -0,0 +1 @@
TestSaveFile
@@ -0,0 +1 @@
TestSaveFileS
@@ -0,0 +1 @@
TestSaveFile
@@ -0,0 +1 @@
TestSaveFileS
+115
View File
@@ -0,0 +1,115 @@
// Copyright 2013 com authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package com
import (
"fmt"
"strconv"
"strings"
"time"
)
// Format unix time int64 to string
func Date(ti int64, format string) string {
t := time.Unix(int64(ti), 0)
return DateT(t, format)
}
// Format unix time string to string
func DateS(ts string, format string) string {
i, _ := strconv.ParseInt(ts, 10, 64)
return Date(i, format)
}
// Format time.Time struct to string
// MM - month - 01
// M - month - 1, single bit
// DD - day - 02
// D - day 2
// YYYY - year - 2006
// YY - year - 06
// HH - 24 hours - 03
// H - 24 hours - 3
// hh - 12 hours - 03
// h - 12 hours - 3
// mm - minute - 04
// m - minute - 4
// ss - second - 05
// s - second = 5
func DateT(t time.Time, format string) string {
res := strings.Replace(format, "MM", t.Format("01"), -1)
res = strings.Replace(res, "M", t.Format("1"), -1)
res = strings.Replace(res, "DD", t.Format("02"), -1)
res = strings.Replace(res, "D", t.Format("2"), -1)
res = strings.Replace(res, "YYYY", t.Format("2006"), -1)
res = strings.Replace(res, "YY", t.Format("06"), -1)
res = strings.Replace(res, "HH", fmt.Sprintf("%02d", t.Hour()), -1)
res = strings.Replace(res, "H", fmt.Sprintf("%d", t.Hour()), -1)
res = strings.Replace(res, "hh", t.Format("03"), -1)
res = strings.Replace(res, "h", t.Format("3"), -1)
res = strings.Replace(res, "mm", t.Format("04"), -1)
res = strings.Replace(res, "m", t.Format("4"), -1)
res = strings.Replace(res, "ss", t.Format("05"), -1)
res = strings.Replace(res, "s", t.Format("5"), -1)
return res
}
// DateFormat pattern rules.
var datePatterns = []string{
// year
"Y", "2006", // A full numeric representation of a year, 4 digits Examples: 1999 or 2003
"y", "06", //A two digit representation of a year Examples: 99 or 03
// month
"m", "01", // Numeric representation of a month, with leading zeros 01 through 12
"n", "1", // Numeric representation of a month, without leading zeros 1 through 12
"M", "Jan", // A short textual representation of a month, three letters Jan through Dec
"F", "January", // A full textual representation of a month, such as January or March January through December
// day
"d", "02", // Day of the month, 2 digits with leading zeros 01 to 31
"j", "2", // Day of the month without leading zeros 1 to 31
// week
"D", "Mon", // A textual representation of a day, three letters Mon through Sun
"l", "Monday", // A full textual representation of the day of the week Sunday through Saturday
// time
"g", "3", // 12-hour format of an hour without leading zeros 1 through 12
"G", "15", // 24-hour format of an hour without leading zeros 0 through 23
"h", "03", // 12-hour format of an hour with leading zeros 01 through 12
"H", "15", // 24-hour format of an hour with leading zeros 00 through 23
"a", "pm", // Lowercase Ante meridiem and Post meridiem am or pm
"A", "PM", // Uppercase Ante meridiem and Post meridiem AM or PM
"i", "04", // Minutes with leading zeros 00 to 59
"s", "05", // Seconds, with leading zeros 00 through 59
// time zone
"T", "MST",
"P", "-07:00",
"O", "-0700",
// RFC 2822
"r", time.RFC1123Z,
}
// Parse Date use PHP time format.
func DateParse(dateString, format string) (time.Time, error) {
replacer := strings.NewReplacer(datePatterns...)
format = replacer.Replace(format)
return time.ParseInLocation(format, dateString, time.Local)
}
+41
View File
@@ -0,0 +1,41 @@
// Copyright 2013 com authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package com
import (
"encoding/base64"
"net/url"
)
// url encode string, is + not %20
func UrlEncode(str string) string {
return url.QueryEscape(str)
}
// url decode string
func UrlDecode(str string) (string, error) {
return url.QueryUnescape(str)
}
// base64 encode
func Base64Encode(str string) string {
return base64.StdEncoding.EncodeToString([]byte(str))
}
// base64 decode
func Base64Decode(str string) (string, error) {
s, e := base64.StdEncoding.DecodeString(str)
return string(s), e
}
@@ -0,0 +1,2 @@
macaron.sublime-project
macaron.sublime-workspace
+191
View File
@@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "[]" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+94
View File
@@ -0,0 +1,94 @@
Macaron [![Build Status](https://drone.io/github.com/Unknwon/macaron/status.png)](https://drone.io/github.com/Unknwon/macaron/latest) [![](http://gocover.io/_badge/github.com/Unknwon/macaron)](http://gocover.io/github.com/Unknwon/macaron)
=======================
![Macaron Logo](https://raw.githubusercontent.com/Unknwon/macaron/master/macaronlogo.png)
Package macaron is a high productive and modular design web framework in Go.
##### Current version: 0.5.4
## Getting Started
To install Macaron:
go get github.com/Unknwon/macaron
The very basic usage of Macaron:
```go
package main
import "github.com/Unknwon/macaron"
func main() {
m := macaron.Classic()
m.Get("/", func() string {
return "Hello world!"
})
m.Run()
}
```
## Features
- Powerful routing with suburl.
- Flexible routes combinations.
- Unlimited nested group routers.
- Directly integrate with existing services.
- Dynamically change template files at runtime.
- Allow to use in-memory template and static files.
- Easy to plugin/unplugin features with modular design.
- Handy dependency injection powered by [inject](https://github.com/codegangsta/inject).
- Better router layer and less reflection make faster speed.
## Middlewares
Middlewares allow you easily plugin/unplugin features for your Macaron applications.
There are already many [middlewares](https://github.com/macaron-contrib) to simplify your work:
- gzip - Gzip compression to all requests
- render - Go template engine
- static - Serves static files
- [binding](https://github.com/macaron-contrib/binding) - Request data binding and validation
- [i18n](https://github.com/macaron-contrib/i18n) - Internationalization and Localization
- [cache](https://github.com/macaron-contrib/cache) - Cache manager
- [session](https://github.com/macaron-contrib/session) - Session manager
- [csrf](https://github.com/macaron-contrib/csrf) - Generates and validates csrf tokens
- [captcha](https://github.com/macaron-contrib/captcha) - Captcha service
- [pongo2](https://github.com/macaron-contrib/pongo2) - Pongo2 template engine support
- [sockets](https://github.com/macaron-contrib/sockets) - WebSockets channels binding
- [bindata](https://github.com/macaron-contrib/bindata) - Embed binary data as static and template files
- [toolbox](https://github.com/macaron-contrib/toolbox) - Health check, pprof, profile and statistic services
- [oauth2](https://github.com/macaron-contrib/oauth2) - OAuth 2.0 backend
- [switcher](https://github.com/macaron-contrib/switcher) - Multiple-site support
- [method](https://github.com/macaron-contrib/method) - HTTP method override
- [permissions2](https://github.com/xyproto/permissions2) - Cookies, users and permissions
- [renders](https://github.com/macaron-contrib/renders) - Beego-like render engine(Macaron has built-in template engine, this is another option)
## Use Cases
- [Gogs](https://github.com/gogits/gogs): Go Git Service
- [Gogs Web](https://github.com/gogits/gogsweb): Gogs official website
- [Go Walker](https://gowalker.org): Go online API documentation
- [Switch](https://github.com/gpmgo/switch): Gopm registry
- [YouGam](http://yougam.com): Online Forum
- [Car Girl](http://qcnl.gzsy.com/): Online campaign
- [Critical Stack Intel](https://intel.criticalstack.com/): A 100% free intel marketplace from Critical Stack, Inc.
## Getting Help
- [API Reference](https://gowalker.org/github.com/Unknwon/macaron)
- [Documentation](http://macaron.gogs.io)
- [FAQs](http://macaron.gogs.io/docs/faqs)
- [![Join the chat at https://gitter.im/Unknwon/macaron](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Unknwon/macaron?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Credits
- Basic design of [Martini](https://github.com/go-martini/martini).
- Router layer of [beego](https://github.com/astaxie/beego).
- Logo is modified by [@insionng](https://github.com/insionng) based on [Tribal Dragon](http://xtremeyamazaki.deviantart.com/art/Tribal-Dragon-27005087).
## License
This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.
+478
View File
@@ -0,0 +1,478 @@
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package macaron
import (
"crypto/md5"
"encoding/hex"
"html/template"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"reflect"
"strconv"
"strings"
"time"
"github.com/Unknwon/com"
"github.com/Unknwon/macaron/inject"
)
// Locale reprents a localization interface.
type Locale interface {
Language() string
Tr(string, ...interface{}) string
}
// RequestBody represents a request body.
type RequestBody struct {
reader io.ReadCloser
}
// Bytes reads and returns content of request body in bytes.
func (rb *RequestBody) Bytes() ([]byte, error) {
return ioutil.ReadAll(rb.reader)
}
// String reads and returns content of request body in string.
func (rb *RequestBody) String() (string, error) {
data, err := rb.Bytes()
return string(data), err
}
// ReadCloser returns a ReadCloser for request body.
func (rb *RequestBody) ReadCloser() io.ReadCloser {
return rb.reader
}
// Request represents an HTTP request received by a server or to be sent by a client.
type Request struct {
*http.Request
}
func (r *Request) Body() *RequestBody {
return &RequestBody{r.Request.Body}
}
// Context represents the runtime context of current request of Macaron instance.
// It is the integration of most frequently used middlewares and helper methods.
type Context struct {
inject.Injector
handlers []Handler
action Handler
index int
*Router
Req Request
Resp ResponseWriter
params Params
Render // Not nil only if you use macaran.Render middleware.
Locale
Data map[string]interface{}
}
func (c *Context) handler() Handler {
if c.index < len(c.handlers) {
return c.handlers[c.index]
}
if c.index == len(c.handlers) {
return c.action
}
panic("invalid index for context handler")
}
func (c *Context) Next() {
c.index += 1
c.run()
}
func (c *Context) Written() bool {
return c.Resp.Written()
}
func (c *Context) run() {
for c.index <= len(c.handlers) {
vals, err := c.Invoke(c.handler())
if err != nil {
panic(err)
}
c.index += 1
// if the handler returned something, write it to the http response
if len(vals) > 0 {
ev := c.GetVal(reflect.TypeOf(ReturnHandler(nil)))
handleReturn := ev.Interface().(ReturnHandler)
handleReturn(c, vals)
}
if c.Written() {
return
}
}
}
// RemoteAddr returns more real IP address.
func (ctx *Context) RemoteAddr() string {
addr := ctx.Req.Header.Get("X-Real-IP")
if len(addr) == 0 {
addr = ctx.Req.Header.Get("X-Forwarded-For")
if addr == "" {
addr = ctx.Req.RemoteAddr
if i := strings.LastIndex(addr, ":"); i > -1 {
addr = addr[:i]
}
}
}
return addr
}
func (ctx *Context) renderHTML(status int, setName, tplName string, data ...interface{}) {
if ctx.Render == nil {
panic("renderer middleware hasn't been registered")
}
if len(data) <= 0 {
ctx.Render.HTMLSet(status, setName, tplName, ctx.Data)
} else if len(data) == 1 {
ctx.Render.HTMLSet(status, setName, tplName, data[0])
} else {
ctx.Render.HTMLSet(status, setName, tplName, data[0], data[1].(HTMLOptions))
}
}
// HTML calls Render.HTML but allows less arguments.
func (ctx *Context) HTML(status int, name string, data ...interface{}) {
ctx.renderHTML(status, _DEFAULT_TPL_SET_NAME, name, data...)
}
// HTML calls Render.HTMLSet but allows less arguments.
func (ctx *Context) HTMLSet(status int, setName, tplName string, data ...interface{}) {
ctx.renderHTML(status, setName, tplName, data...)
}
func (ctx *Context) Redirect(location string, status ...int) {
code := http.StatusFound
if len(status) == 1 {
code = status[0]
}
http.Redirect(ctx.Resp, ctx.Req.Request, location, code)
}
// Query querys form parameter.
func (ctx *Context) Query(name string) string {
if ctx.Req.Form == nil {
ctx.Req.ParseForm()
}
return ctx.Req.Form.Get(name)
}
// QueryTrim querys and trims spaces form parameter.
func (ctx *Context) QueryTrim(name string) string {
return strings.TrimSpace(ctx.Query(name))
}
// QueryStrings returns a list of results by given query name.
func (ctx *Context) QueryStrings(name string) []string {
if ctx.Req.Form == nil {
ctx.Req.ParseForm()
}
vals, ok := ctx.Req.Form[name]
if !ok {
return []string{}
}
return vals
}
// QueryEscape returns escapred query result.
func (ctx *Context) QueryEscape(name string) string {
return template.HTMLEscapeString(ctx.Query(name))
}
// QueryInt returns query result in int type.
func (ctx *Context) QueryInt(name string) int {
return com.StrTo(ctx.Query(name)).MustInt()
}
// QueryInt64 returns query result in int64 type.
func (ctx *Context) QueryInt64(name string) int64 {
return com.StrTo(ctx.Query(name)).MustInt64()
}
// QueryFloat64 returns query result in float64 type.
func (ctx *Context) QueryFloat64(name string) float64 {
v, _ := strconv.ParseFloat(ctx.Query(name), 64)
return v
}
// Params returns value of given param name.
// e.g. ctx.Params(":uid") or ctx.Params("uid")
func (ctx *Context) Params(name string) string {
if len(name) == 0 {
return ""
}
if name[0] != '*' && name[0] != ':' {
name = ":" + name
}
return ctx.params[name]
}
// SetParams sets value of param with given name.
func (ctx *Context) SetParams(name, val string) {
if !strings.HasPrefix(name, ":") {
name = ":" + name
}
ctx.params[name] = val
}
// ParamsEscape returns escapred params result.
// e.g. ctx.ParamsEscape(":uname")
func (ctx *Context) ParamsEscape(name string) string {
return template.HTMLEscapeString(ctx.Params(name))
}
// ParamsInt returns params result in int type.
// e.g. ctx.ParamsInt(":uid")
func (ctx *Context) ParamsInt(name string) int {
return com.StrTo(ctx.Params(name)).MustInt()
}
// ParamsInt64 returns params result in int64 type.
// e.g. ctx.ParamsInt64(":uid")
func (ctx *Context) ParamsInt64(name string) int64 {
return com.StrTo(ctx.Params(name)).MustInt64()
}
// ParamsFloat64 returns params result in int64 type.
// e.g. ctx.ParamsFloat64(":uid")
func (ctx *Context) ParamsFloat64(name string) float64 {
v, _ := strconv.ParseFloat(ctx.Params(name), 64)
return v
}
// GetFile returns information about user upload file by given form field name.
func (ctx *Context) GetFile(name string) (multipart.File, *multipart.FileHeader, error) {
return ctx.Req.FormFile(name)
}
// SetCookie sets given cookie value to response header.
// FIXME: IE support? http://golanghome.com/post/620#reply2
func (ctx *Context) SetCookie(name string, value string, others ...interface{}) {
cookie := http.Cookie{}
cookie.Name = name
cookie.Value = url.QueryEscape(value)
if len(others) > 0 {
switch v := others[0].(type) {
case int:
cookie.MaxAge = v
case int64:
cookie.MaxAge = int(v)
case int32:
cookie.MaxAge = int(v)
}
}
cookie.Path = "/"
if len(others) > 1 {
if v, ok := others[1].(string); ok && len(v) > 0 {
cookie.Path = v
}
}
if len(others) > 2 {
if v, ok := others[2].(string); ok && len(v) > 0 {
cookie.Domain = v
}
}
if len(others) > 3 {
switch v := others[3].(type) {
case bool:
cookie.Secure = v
default:
if others[3] != nil {
cookie.Secure = true
}
}
}
if len(others) > 4 {
if v, ok := others[4].(bool); ok && v {
cookie.HttpOnly = true
}
}
ctx.Resp.Header().Add("Set-Cookie", cookie.String())
}
// GetCookie returns given cookie value from request header.
func (ctx *Context) GetCookie(name string) string {
cookie, err := ctx.Req.Cookie(name)
if err != nil {
return ""
}
val, _ := url.QueryUnescape(cookie.Value)
return val
}
// GetCookieInt returns cookie result in int type.
func (ctx *Context) GetCookieInt(name string) int {
return com.StrTo(ctx.GetCookie(name)).MustInt()
}
// GetCookieInt64 returns cookie result in int64 type.
func (ctx *Context) GetCookieInt64(name string) int64 {
return com.StrTo(ctx.GetCookie(name)).MustInt64()
}
// GetCookieFloat64 returns cookie result in float64 type.
func (ctx *Context) GetCookieFloat64(name string) float64 {
v, _ := strconv.ParseFloat(ctx.GetCookie(name), 64)
return v
}
var defaultCookieSecret string
// SetDefaultCookieSecret sets global default secure cookie secret.
func (m *Macaron) SetDefaultCookieSecret(secret string) {
defaultCookieSecret = secret
}
// SetSecureCookie sets given cookie value to response header with default secret string.
func (ctx *Context) SetSecureCookie(name, value string, others ...interface{}) {
ctx.SetSuperSecureCookie(defaultCookieSecret, name, value, others...)
}
// GetSecureCookie returns given cookie value from request header with default secret string.
func (ctx *Context) GetSecureCookie(key string) (string, bool) {
return ctx.GetSuperSecureCookie(defaultCookieSecret, key)
}
// SetSuperSecureCookie sets given cookie value to response header with secret string.
func (ctx *Context) SetSuperSecureCookie(secret, name, value string, others ...interface{}) {
m := md5.Sum([]byte(secret))
secret = hex.EncodeToString(m[:])
text, err := com.AESEncrypt([]byte(secret), []byte(value))
if err != nil {
panic("error encrypting cookie: " + err.Error())
}
ctx.SetCookie(name, hex.EncodeToString(text), others...)
}
// GetSuperSecureCookie returns given cookie value from request header with secret string.
func (ctx *Context) GetSuperSecureCookie(secret, key string) (string, bool) {
val := ctx.GetCookie(key)
if val == "" {
return "", false
}
data, err := hex.DecodeString(val)
if err != nil {
return "", false
}
m := md5.Sum([]byte(secret))
secret = hex.EncodeToString(m[:])
text, err := com.AESDecrypt([]byte(secret), data)
return string(text), err == nil
}
func (ctx *Context) setRawContentHeader() {
ctx.Resp.Header().Set("Content-Description", "Raw content")
ctx.Resp.Header().Set("Content-Type", "text/plain")
ctx.Resp.Header().Set("Expires", "0")
ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
ctx.Resp.Header().Set("Pragma", "public")
}
// ServeContent serves given content to response.
func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interface{}) {
modtime := time.Now()
for _, p := range params {
switch v := p.(type) {
case time.Time:
modtime = v
}
}
ctx.setRawContentHeader()
http.ServeContent(ctx.Resp, ctx.Req.Request, name, modtime, r)
}
// ServeFileContent serves given file as content to response.
func (ctx *Context) ServeFileContent(file string, names ...string) {
var name string
if len(names) > 0 {
name = names[0]
} else {
name = path.Base(file)
}
f, err := os.Open(file)
if err != nil {
if Env == PROD {
http.Error(ctx.Resp, "Internal Server Error", 500)
} else {
http.Error(ctx.Resp, err.Error(), 500)
}
return
}
defer f.Close()
ctx.setRawContentHeader()
http.ServeContent(ctx.Resp, ctx.Req.Request, name, time.Now(), f)
}
// ServeFile serves given file to response.
func (ctx *Context) ServeFile(file string, names ...string) {
var name string
if len(names) > 0 {
name = names[0]
} else {
name = path.Base(file)
}
ctx.Resp.Header().Set("Content-Description", "File Transfer")
ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+name)
ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
ctx.Resp.Header().Set("Expires", "0")
ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
ctx.Resp.Header().Set("Pragma", "public")
http.ServeFile(ctx.Resp, ctx.Req.Request, file)
}
// ChangeStaticPath changes static path from old to new one.
func (ctx *Context) ChangeStaticPath(oldPath, newPath string) {
if !filepath.IsAbs(oldPath) {
oldPath = filepath.Join(Root, oldPath)
}
dir := statics.Get(oldPath)
if dir != nil {
statics.Delete(oldPath)
if !filepath.IsAbs(newPath) {
newPath = filepath.Join(Root, newPath)
}
*dir = http.Dir(newPath)
statics.Set(dir)
}
}
+370
View File
@@ -0,0 +1,370 @@
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package macaron
import (
"bytes"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"time"
"github.com/Unknwon/com"
. "github.com/smartystreets/goconvey/convey"
)
func Test_Context(t *testing.T) {
Convey("Do advanced encapsulation operations", t, func() {
m := Classic()
m.Use(Renderers(RenderOptions{
Directory: "fixtures/basic",
}, "fixtures/basic2"))
Convey("Get request body", func() {
m.Get("/body1", func(ctx *Context) {
data, err := ioutil.ReadAll(ctx.Req.Body().ReadCloser())
So(err, ShouldBeNil)
So(string(data), ShouldEqual, "This is my request body")
})
m.Get("/body2", func(ctx *Context) {
data, err := ctx.Req.Body().Bytes()
So(err, ShouldBeNil)
So(string(data), ShouldEqual, "This is my request body")
})
m.Get("/body3", func(ctx *Context) {
data, err := ctx.Req.Body().String()
So(err, ShouldBeNil)
So(data, ShouldEqual, "This is my request body")
})
for i := 1; i <= 3; i++ {
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/body"+com.ToStr(i), nil)
req.Body = ioutil.NopCloser(bytes.NewBufferString("This is my request body"))
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
}
})
Convey("Get remote IP address", func() {
m.Get("/remoteaddr", func(ctx *Context) string {
return ctx.RemoteAddr()
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/remoteaddr", nil)
req.RemoteAddr = "127.0.0.1:3333"
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "127.0.0.1")
})
Convey("Render HTML", func() {
Convey("Normal HTML", func() {
m.Get("/html", func(ctx *Context) {
ctx.HTML(304, "hello", "Unknwon") // 304 for logger test.
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/html", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "<h1>Hello Unknwon</h1>")
})
Convey("HTML template set", func() {
m.Get("/html2", func(ctx *Context) {
ctx.Data["Name"] = "Unknwon"
ctx.HTMLSet(200, "basic2", "hello2")
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/html2", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "<h1>Hello Unknwon</h1>")
})
Convey("With layout", func() {
m.Get("/layout", func(ctx *Context) {
ctx.HTML(200, "hello", "Unknwon", HTMLOptions{"layout"})
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/layout", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "head<h1>Hello Unknwon</h1>foot")
})
})
Convey("Parse from and query", func() {
m.Get("/query", func(ctx *Context) string {
var buf bytes.Buffer
buf.WriteString(ctx.QueryTrim("name") + " ")
buf.WriteString(ctx.QueryEscape("name") + " ")
buf.WriteString(com.ToStr(ctx.QueryInt("int")) + " ")
buf.WriteString(com.ToStr(ctx.QueryInt64("int64")) + " ")
buf.WriteString(com.ToStr(ctx.QueryFloat64("float64")) + " ")
return buf.String()
})
m.Get("/query2", func(ctx *Context) string {
var buf bytes.Buffer
buf.WriteString(strings.Join(ctx.QueryStrings("list"), ",") + " ")
buf.WriteString(strings.Join(ctx.QueryStrings("404"), ",") + " ")
return buf.String()
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/query?name=Unknwon&int=12&int64=123&float64=1.25", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "Unknwon Unknwon 12 123 1.25 ")
resp = httptest.NewRecorder()
req, err = http.NewRequest("GET", "/query2?list=item1&list=item2", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "item1,item2 ")
})
Convey("URL parameter", func() {
m.Get("/:name/:int/:int64/:float64", func(ctx *Context) string {
var buf bytes.Buffer
ctx.SetParams("name", ctx.Params("name"))
buf.WriteString(ctx.Params(""))
buf.WriteString(ctx.Params(":name") + " ")
buf.WriteString(ctx.ParamsEscape(":name") + " ")
buf.WriteString(com.ToStr(ctx.ParamsInt(":int")) + " ")
buf.WriteString(com.ToStr(ctx.ParamsInt64(":int64")) + " ")
buf.WriteString(com.ToStr(ctx.ParamsFloat64(":float64")) + " ")
return buf.String()
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/user/1/13/1.24", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "user user 1 13 1.24 ")
})
Convey("Get file", func() {
m.Get("/getfile", func(ctx *Context) {
ctx.GetFile("hi")
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/getfile", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
})
Convey("Set and get cookie", func() {
m.Get("/set", func(ctx *Context) {
ctx.SetCookie("user", "Unknwon", 1, "/", "localhost", true, true)
ctx.SetCookie("user", "Unknwon", int32(1), "/", "localhost", 1)
ctx.SetCookie("user", "Unknwon", int64(1))
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/set", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Header().Get("Set-Cookie"), ShouldEqual, "user=Unknwon; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure")
m.Get("/get", func(ctx *Context) string {
ctx.GetCookie("404")
So(ctx.GetCookieInt("uid"), ShouldEqual, 1)
So(ctx.GetCookieInt64("uid"), ShouldEqual, 1)
So(ctx.GetCookieFloat64("balance"), ShouldEqual, 1.25)
return ctx.GetCookie("user")
})
resp = httptest.NewRecorder()
req, err = http.NewRequest("GET", "/get", nil)
So(err, ShouldBeNil)
req.Header.Set("Cookie", "user=Unknwon; uid=1; balance=1.25")
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "Unknwon")
})
Convey("Set and get secure cookie", func() {
m.SetDefaultCookieSecret("macaron")
m.Get("/set", func(ctx *Context) {
ctx.SetSecureCookie("user", "Unknwon", 1)
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/set", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
cookie := resp.Header().Get("Set-Cookie")
m.Get("/get", func(ctx *Context) string {
name, ok := ctx.GetSecureCookie("user")
So(ok, ShouldBeTrue)
return name
})
resp = httptest.NewRecorder()
req, err = http.NewRequest("GET", "/get", nil)
So(err, ShouldBeNil)
req.Header.Set("Cookie", cookie)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "Unknwon")
})
Convey("Serve files", func() {
m.Get("/file", func(ctx *Context) {
ctx.ServeFile("fixtures/custom_funcs/index.tmpl")
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/file", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "{{ myCustomFunc }}")
m.Get("/file2", func(ctx *Context) {
ctx.ServeFile("fixtures/custom_funcs/index.tmpl", "ok.tmpl")
})
resp = httptest.NewRecorder()
req, err = http.NewRequest("GET", "/file2", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "{{ myCustomFunc }}")
})
Convey("Serve file content", func() {
m.Get("/file", func(ctx *Context) {
ctx.ServeFileContent("fixtures/custom_funcs/index.tmpl")
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/file", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "{{ myCustomFunc }}")
m.Get("/file2", func(ctx *Context) {
ctx.ServeFileContent("fixtures/custom_funcs/index.tmpl", "ok.tmpl")
})
resp = httptest.NewRecorder()
req, err = http.NewRequest("GET", "/file2", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "{{ myCustomFunc }}")
m.Get("/file3", func(ctx *Context) {
ctx.ServeFileContent("404.tmpl")
})
resp = httptest.NewRecorder()
req, err = http.NewRequest("GET", "/file3", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "open 404.tmpl: no such file or directory\n")
So(resp.Code, ShouldEqual, 500)
})
Convey("Serve content", func() {
m.Get("/content", func(ctx *Context) {
ctx.ServeContent("content1", bytes.NewReader([]byte("Hello world!")))
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/content", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "Hello world!")
m.Get("/content2", func(ctx *Context) {
ctx.ServeContent("content1", bytes.NewReader([]byte("Hello world!")), time.Now())
})
resp = httptest.NewRecorder()
req, err = http.NewRequest("GET", "/content2", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "Hello world!")
})
})
}
func Test_Context_Render(t *testing.T) {
Convey("Invalid render", t, func() {
defer func() {
So(recover(), ShouldNotBeNil)
}()
m := New()
m.Get("/", func(ctx *Context) {
ctx.HTML(200, "hey")
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
})
}
func Test_Context_Redirect(t *testing.T) {
Convey("Context with default redirect", t, func() {
url, err := url.Parse("http://localhost/path/one")
So(err, ShouldBeNil)
resp := httptest.NewRecorder()
req := http.Request{
Method: "GET",
URL: url,
}
ctx := &Context{
Req: Request{&req},
Resp: NewResponseWriter(resp),
Data: make(map[string]interface{}),
}
ctx.Redirect("two")
So(resp.Code, ShouldEqual, http.StatusFound)
So(resp.HeaderMap["Location"][0], ShouldEqual, "/path/two")
})
Convey("Context with custom redirect", t, func() {
url, err := url.Parse("http://localhost/path/one")
So(err, ShouldBeNil)
resp := httptest.NewRecorder()
req := http.Request{
Method: "GET",
URL: url,
}
ctx := &Context{
Req: Request{&req},
Resp: NewResponseWriter(resp),
Data: make(map[string]interface{}),
}
ctx.Redirect("two", 307)
So(resp.Code, ShouldEqual, http.StatusTemporaryRedirect)
So(resp.HeaderMap["Location"][0], ShouldEqual, "/path/two")
})
}
@@ -0,0 +1 @@
<h1>Admin {{.}}</h1>
@@ -0,0 +1 @@
another head{{ yield }}another foot
@@ -0,0 +1 @@
<h1>{{ . }}</h1>
@@ -0,0 +1 @@
{{ current }} head{{ yield }}{{ current }} foot
@@ -0,0 +1 @@
<h1>Hello {[{.}]}</h1>
@@ -0,0 +1 @@
<h1>Hello {{.}}</h1>
@@ -0,0 +1 @@
Hypertext!
@@ -0,0 +1 @@
head{{ yield }}foot
@@ -0,0 +1 @@
<h1>What's up, {{.}}</h1>
@@ -0,0 +1 @@
<h1>Hello {{.Name}}</h1>
@@ -0,0 +1 @@
{{ myCustomFunc }}
+81
View File
@@ -0,0 +1,81 @@
// Copyright 2013 Martini Authors
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package macaron
import (
"bufio"
"compress/gzip"
"fmt"
"net"
"net/http"
"strings"
)
const (
HeaderAcceptEncoding = "Accept-Encoding"
HeaderContentEncoding = "Content-Encoding"
HeaderContentLength = "Content-Length"
HeaderContentType = "Content-Type"
HeaderVary = "Vary"
)
// Gziper returns a Handler that adds gzip compression to all requests.
// Make sure to include the Gzip middleware above other middleware
// that alter the response body (like the render middleware).
func Gziper() Handler {
return func(ctx *Context) {
if !strings.Contains(ctx.Req.Header.Get(HeaderAcceptEncoding), "gzip") {
return
}
headers := ctx.Resp.Header()
headers.Set(HeaderContentEncoding, "gzip")
headers.Set(HeaderVary, HeaderAcceptEncoding)
gz := gzip.NewWriter(ctx.Resp)
defer gz.Close()
gzw := gzipResponseWriter{gz, ctx.Resp}
ctx.Resp = gzw
ctx.MapTo(gzw, (*http.ResponseWriter)(nil))
ctx.Next()
// delete content length after we know we have been written to
gzw.Header().Del("Content-Length")
}
}
type gzipResponseWriter struct {
w *gzip.Writer
ResponseWriter
}
func (grw gzipResponseWriter) Write(p []byte) (int, error) {
if len(grw.Header().Get(HeaderContentType)) == 0 {
grw.Header().Set(HeaderContentType, http.DetectContentType(p))
}
return grw.w.Write(p)
}
func (grw gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
hijacker, ok := grw.ResponseWriter.(http.Hijacker)
if !ok {
return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface")
}
return hijacker.Hijack()
}
+65
View File
@@ -0,0 +1,65 @@
// Copyright 2013 Martini Authors
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package macaron
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func Test_Gzip(t *testing.T) {
Convey("Gzip response content", t, func() {
before := false
m := New()
m.Use(Gziper())
m.Use(func(r http.ResponseWriter) {
r.(ResponseWriter).Before(func(rw ResponseWriter) {
before = true
})
})
m.Get("/", func() string { return "hello wolrd!" })
// Not yet gzip.
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
_, ok := resp.HeaderMap[HeaderContentEncoding]
So(ok, ShouldBeFalse)
ce := resp.Header().Get(HeaderContentEncoding)
So(strings.EqualFold(ce, "gzip"), ShouldBeFalse)
// Gzip now.
resp = httptest.NewRecorder()
req.Header.Set(HeaderAcceptEncoding, "gzip")
m.ServeHTTP(resp, req)
_, ok = resp.HeaderMap[HeaderContentEncoding]
So(ok, ShouldBeTrue)
ce = resp.Header().Get(HeaderContentEncoding)
So(strings.EqualFold(ce, "gzip"), ShouldBeTrue)
So(before, ShouldBeTrue)
})
}
+4
View File
@@ -0,0 +1,4 @@
inject
======
Dependency injection for go
+187
View File
@@ -0,0 +1,187 @@
// Package inject provides utilities for mapping and injecting dependencies in various ways.
package inject
import (
"fmt"
"reflect"
)
// Injector represents an interface for mapping and injecting dependencies into structs
// and function arguments.
type Injector interface {
Applicator
Invoker
TypeMapper
// SetParent sets the parent of the injector. If the injector cannot find a
// dependency in its Type map it will check its parent before returning an
// error.
SetParent(Injector)
}
// Applicator represents an interface for mapping dependencies to a struct.
type Applicator interface {
// Maps dependencies in the Type map to each field in the struct
// that is tagged with 'inject'. Returns an error if the injection
// fails.
Apply(interface{}) error
}
// Invoker represents an interface for calling functions via reflection.
type Invoker interface {
// Invoke attempts to call the interface{} provided as a function,
// providing dependencies for function arguments based on Type. Returns
// a slice of reflect.Value representing the returned values of the function.
// Returns an error if the injection fails.
Invoke(interface{}) ([]reflect.Value, error)
}
// TypeMapper represents an interface for mapping interface{} values based on type.
type TypeMapper interface {
// Maps the interface{} value based on its immediate type from reflect.TypeOf.
Map(interface{}) TypeMapper
// Maps the interface{} value based on the pointer of an Interface provided.
// This is really only useful for mapping a value as an interface, as interfaces
// cannot at this time be referenced directly without a pointer.
MapTo(interface{}, interface{}) TypeMapper
// Provides a possibility to directly insert a mapping based on type and value.
// This makes it possible to directly map type arguments not possible to instantiate
// with reflect like unidirectional channels.
Set(reflect.Type, reflect.Value) TypeMapper
// Returns the Value that is mapped to the current type. Returns a zeroed Value if
// the Type has not been mapped.
GetVal(reflect.Type) reflect.Value
}
type injector struct {
values map[reflect.Type]reflect.Value
parent Injector
}
// InterfaceOf dereferences a pointer to an Interface type.
// It panics if value is not an pointer to an interface.
func InterfaceOf(value interface{}) reflect.Type {
t := reflect.TypeOf(value)
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Interface {
panic("Called inject.InterfaceOf with a value that is not a pointer to an interface. (*MyInterface)(nil)")
}
return t
}
// New returns a new Injector.
func New() Injector {
return &injector{
values: make(map[reflect.Type]reflect.Value),
}
}
// Invoke attempts to call the interface{} provided as a function,
// providing dependencies for function arguments based on Type.
// Returns a slice of reflect.Value representing the returned values of the function.
// Returns an error if the injection fails.
// It panics if f is not a function
func (inj *injector) Invoke(f interface{}) ([]reflect.Value, error) {
t := reflect.TypeOf(f)
var in = make([]reflect.Value, t.NumIn()) //Panic if t is not kind of Func
for i := 0; i < t.NumIn(); i++ {
argType := t.In(i)
val := inj.GetVal(argType)
if !val.IsValid() {
return nil, fmt.Errorf("Value not found for type %v", argType)
}
in[i] = val
}
return reflect.ValueOf(f).Call(in), nil
}
// Maps dependencies in the Type map to each field in the struct
// that is tagged with 'inject'.
// Returns an error if the injection fails.
func (inj *injector) Apply(val interface{}) error {
v := reflect.ValueOf(val)
for v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return nil // Should not panic here ?
}
t := v.Type()
for i := 0; i < v.NumField(); i++ {
f := v.Field(i)
structField := t.Field(i)
if f.CanSet() && (structField.Tag == "inject" || structField.Tag.Get("inject") != "") {
ft := f.Type()
v := inj.GetVal(ft)
if !v.IsValid() {
return fmt.Errorf("Value not found for type %v", ft)
}
f.Set(v)
}
}
return nil
}
// Maps the concrete value of val to its dynamic type using reflect.TypeOf,
// It returns the TypeMapper registered in.
func (i *injector) Map(val interface{}) TypeMapper {
i.values[reflect.TypeOf(val)] = reflect.ValueOf(val)
return i
}
func (i *injector) MapTo(val interface{}, ifacePtr interface{}) TypeMapper {
i.values[InterfaceOf(ifacePtr)] = reflect.ValueOf(val)
return i
}
// Maps the given reflect.Type to the given reflect.Value and returns
// the Typemapper the mapping has been registered in.
func (i *injector) Set(typ reflect.Type, val reflect.Value) TypeMapper {
i.values[typ] = val
return i
}
func (i *injector) GetVal(t reflect.Type) reflect.Value {
val := i.values[t]
if val.IsValid() {
return val
}
// no concrete types found, try to find implementors
// if t is an interface
if t.Kind() == reflect.Interface {
for k, v := range i.values {
if k.Implements(t) {
val = v
break
}
}
}
// Still no type found, try to look it up on the parent
if !val.IsValid() && i.parent != nil {
val = i.parent.GetVal(t)
}
return val
}
func (i *injector) SetParent(parent Injector) {
i.parent = parent
}
@@ -0,0 +1 @@
ignore
+174
View File
@@ -0,0 +1,174 @@
// Copyright 2013 Martini Authors
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package inject_test
import (
"fmt"
"reflect"
"testing"
"github.com/Unknwon/macaron/inject"
)
type SpecialString interface {
}
type TestStruct struct {
Dep1 string `inject:"t" json:"-"`
Dep2 SpecialString `inject`
Dep3 string
}
type Greeter struct {
Name string
}
func (g *Greeter) String() string {
return "Hello, My name is" + g.Name
}
/* Test Helpers */
func expect(t *testing.T, a interface{}, b interface{}) {
if a != b {
t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
}
}
func refute(t *testing.T, a interface{}, b interface{}) {
if a == b {
t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
}
}
func Test_InjectorInvoke(t *testing.T) {
injector := inject.New()
expect(t, injector == nil, false)
dep := "some dependency"
injector.Map(dep)
dep2 := "another dep"
injector.MapTo(dep2, (*SpecialString)(nil))
dep3 := make(chan *SpecialString)
dep4 := make(chan *SpecialString)
typRecv := reflect.ChanOf(reflect.RecvDir, reflect.TypeOf(dep3).Elem())
typSend := reflect.ChanOf(reflect.SendDir, reflect.TypeOf(dep4).Elem())
injector.Set(typRecv, reflect.ValueOf(dep3))
injector.Set(typSend, reflect.ValueOf(dep4))
_, err := injector.Invoke(func(d1 string, d2 SpecialString, d3 <-chan *SpecialString, d4 chan<- *SpecialString) {
expect(t, d1, dep)
expect(t, d2, dep2)
expect(t, reflect.TypeOf(d3).Elem(), reflect.TypeOf(dep3).Elem())
expect(t, reflect.TypeOf(d4).Elem(), reflect.TypeOf(dep4).Elem())
expect(t, reflect.TypeOf(d3).ChanDir(), reflect.RecvDir)
expect(t, reflect.TypeOf(d4).ChanDir(), reflect.SendDir)
})
expect(t, err, nil)
}
func Test_InjectorInvokeReturnValues(t *testing.T) {
injector := inject.New()
expect(t, injector == nil, false)
dep := "some dependency"
injector.Map(dep)
dep2 := "another dep"
injector.MapTo(dep2, (*SpecialString)(nil))
result, err := injector.Invoke(func(d1 string, d2 SpecialString) string {
expect(t, d1, dep)
expect(t, d2, dep2)
return "Hello world"
})
expect(t, result[0].String(), "Hello world")
expect(t, err, nil)
}
func Test_InjectorApply(t *testing.T) {
injector := inject.New()
injector.Map("a dep").MapTo("another dep", (*SpecialString)(nil))
s := TestStruct{}
err := injector.Apply(&s)
expect(t, err, nil)
expect(t, s.Dep1, "a dep")
expect(t, s.Dep2, "another dep")
}
func Test_InterfaceOf(t *testing.T) {
iType := inject.InterfaceOf((*SpecialString)(nil))
expect(t, iType.Kind(), reflect.Interface)
iType = inject.InterfaceOf((**SpecialString)(nil))
expect(t, iType.Kind(), reflect.Interface)
// Expecting nil
defer func() {
rec := recover()
refute(t, rec, nil)
}()
iType = inject.InterfaceOf((*testing.T)(nil))
}
func Test_InjectorSet(t *testing.T) {
injector := inject.New()
typ := reflect.TypeOf("string")
typSend := reflect.ChanOf(reflect.SendDir, typ)
typRecv := reflect.ChanOf(reflect.RecvDir, typ)
// instantiating unidirectional channels is not possible using reflect
// http://golang.org/src/pkg/reflect/value.go?s=60463:60504#L2064
chanRecv := reflect.MakeChan(reflect.ChanOf(reflect.BothDir, typ), 0)
chanSend := reflect.MakeChan(reflect.ChanOf(reflect.BothDir, typ), 0)
injector.Set(typSend, chanSend)
injector.Set(typRecv, chanRecv)
expect(t, injector.GetVal(typSend).IsValid(), true)
expect(t, injector.GetVal(typRecv).IsValid(), true)
expect(t, injector.GetVal(chanSend.Type()).IsValid(), false)
}
func Test_InjectorGet(t *testing.T) {
injector := inject.New()
injector.Map("some dependency")
expect(t, injector.GetVal(reflect.TypeOf("string")).IsValid(), true)
expect(t, injector.GetVal(reflect.TypeOf(11)).IsValid(), false)
}
func Test_InjectorSetParent(t *testing.T) {
injector := inject.New()
injector.MapTo("another dep", (*SpecialString)(nil))
injector2 := inject.New()
injector2.SetParent(injector)
expect(t, injector2.GetVal(inject.InterfaceOf((*SpecialString)(nil))).IsValid(), true)
}
func TestInjectImplementors(t *testing.T) {
injector := inject.New()
g := &Greeter{"Jeremy"}
injector.Map(g)
expect(t, injector.GetVal(inject.InterfaceOf((*fmt.Stringer)(nil))).IsValid(), true)
}
+61
View File
@@ -0,0 +1,61 @@
// Copyright 2013 Martini Authors
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package macaron
import (
"fmt"
"log"
"net/http"
"runtime"
"time"
)
var ColorLog = true
func init() {
ColorLog = runtime.GOOS != "windows"
}
// Logger returns a middleware handler that logs the request as it goes in and the response as it goes out.
func Logger() Handler {
return func(ctx *Context, log *log.Logger) {
start := time.Now()
log.Printf("Started %s %s for %s", ctx.Req.Method, ctx.Req.RequestURI, ctx.RemoteAddr())
rw := ctx.Resp.(ResponseWriter)
ctx.Next()
content := fmt.Sprintf("Completed %s %v %s in %v", ctx.Req.RequestURI, rw.Status(), http.StatusText(rw.Status()), time.Since(start))
if ColorLog {
switch rw.Status() {
case 200, 201, 202:
content = fmt.Sprintf("\033[1;32m%s\033[0m", content)
case 301, 302:
content = fmt.Sprintf("\033[1;37m%s\033[0m", content)
case 304:
content = fmt.Sprintf("\033[1;33m%s\033[0m", content)
case 401, 403:
content = fmt.Sprintf("\033[4;31m%s\033[0m", content)
case 404:
content = fmt.Sprintf("\033[1;31m%s\033[0m", content)
case 500:
content = fmt.Sprintf("\033[1;36m%s\033[0m", content)
}
}
log.Println(content)
}
}
+67
View File
@@ -0,0 +1,67 @@
// Copyright 2013 Martini Authors
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package macaron
import (
"bytes"
"log"
"net/http"
"net/http/httptest"
"testing"
"github.com/Unknwon/com"
. "github.com/smartystreets/goconvey/convey"
)
func Test_Logger(t *testing.T) {
Convey("Global logger", t, func() {
buf := bytes.NewBufferString("")
m := New()
m.Map(log.New(buf, "[Macaron] ", 0))
m.Use(Logger())
m.Use(func(res http.ResponseWriter) {
res.WriteHeader(http.StatusNotFound)
})
m.Get("/", func() {})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "http://localhost:4000/", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusNotFound)
So(len(buf.String()), ShouldBeGreaterThan, 0)
})
if ColorLog {
Convey("Color console output", t, func() {
m := Classic()
m.Get("/:code:int", func(ctx *Context) (int, string) {
return ctx.ParamsInt(":code"), ""
})
// Just for testing if logger would capture.
codes := []int{200, 201, 202, 301, 302, 304, 401, 403, 404, 500}
for _, code := range codes {
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "http://localhost:4000/"+com.ToStr(code), nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, code)
}
})
}
}
+273
View File
@@ -0,0 +1,273 @@
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
// Package macaron is a high productive and modular design web framework in Go.
package macaron
import (
"io"
"log"
"net/http"
"os"
"reflect"
"strings"
"github.com/Unknwon/com"
"gopkg.in/ini.v1"
"github.com/Unknwon/macaron/inject"
)
const _VERSION = "0.5.4.0318"
func Version() string {
return _VERSION
}
// Handler can be any callable function.
// Macaron attempts to inject services into the handler's argument list,
// and panics if an argument could not be fullfilled via dependency injection.
type Handler interface{}
// validateHandler makes sure a handler is a callable function,
// and panics if it is not.
func validateHandler(h Handler) {
if reflect.TypeOf(h).Kind() != reflect.Func {
panic("Macaron handler must be a callable function")
}
}
// validateHandlers makes sure handlers are callable functions,
// and panics if any of them is not.
func validateHandlers(handlers []Handler) {
for _, h := range handlers {
validateHandler(h)
}
}
// Macaron represents the top level web application.
// inject.Injector methods can be invoked to map services on a global level.
type Macaron struct {
inject.Injector
befores []BeforeHandler
handlers []Handler
action Handler
urlPrefix string // For suburl support.
*Router
logger *log.Logger
}
// NewWithLogger creates a bare bones Macaron instance.
// Use this method if you want to have full control over the middleware that is used.
// You can specify logger output writer with this function.
func NewWithLogger(out io.Writer) *Macaron {
m := &Macaron{
Injector: inject.New(),
action: func() {},
Router: NewRouter(),
logger: log.New(out, "[Macaron] ", 0),
}
m.Router.m = m
m.Map(m.logger)
m.Map(defaultReturnHandler())
m.notFound = func(resp http.ResponseWriter, req *http.Request) {
c := m.createContext(resp, req)
c.handlers = append(c.handlers, http.NotFound)
c.run()
}
return m
}
// New creates a bare bones Macaron instance.
// Use this method if you want to have full control over the middleware that is used.
func New() *Macaron {
return NewWithLogger(os.Stdout)
}
// Classic creates a classic Macaron with some basic default middleware:
// mocaron.Logger, mocaron.Recovery and mocaron.Static.
func Classic() *Macaron {
m := New()
m.Use(Logger())
m.Use(Recovery())
m.Use(Static("public"))
return m
}
// Handlers sets the entire middleware stack with the given Handlers.
// This will clear any current middleware handlers,
// and panics if any of the handlers is not a callable function
func (m *Macaron) Handlers(handlers ...Handler) {
m.handlers = make([]Handler, 0)
for _, handler := range handlers {
m.Use(handler)
}
}
// Action sets the handler that will be called after all the middleware has been invoked.
// This is set to macaron.Router in a macaron.Classic().
func (m *Macaron) Action(handler Handler) {
validateHandler(handler)
m.action = handler
}
// BeforeHandler represents a handler executes at beginning of every request.
// Macaron stops future process when it returns true.
type BeforeHandler func(rw http.ResponseWriter, req *http.Request) bool
func (m *Macaron) Before(handler BeforeHandler) {
m.befores = append(m.befores, handler)
}
// Use adds a middleware Handler to the stack,
// and panics if the handler is not a callable func.
// Middleware Handlers are invoked in the order that they are added.
func (m *Macaron) Use(handler Handler) {
validateHandler(handler)
m.handlers = append(m.handlers, handler)
}
func (m *Macaron) createContext(rw http.ResponseWriter, req *http.Request) *Context {
c := &Context{
Injector: inject.New(),
handlers: m.handlers,
action: m.action,
index: 0,
Router: m.Router,
Req: Request{req},
Resp: NewResponseWriter(rw),
Data: make(map[string]interface{}),
}
c.SetParent(m)
c.Map(c)
c.MapTo(c.Resp, (*http.ResponseWriter)(nil))
c.Map(req)
return c
}
// ServeHTTP is the HTTP Entry point for a Macaron instance.
// Useful if you want to control your own HTTP server.
// Be aware that none of middleware will run without registering any router.
func (m *Macaron) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
req.URL.Path = strings.TrimPrefix(req.URL.Path, m.urlPrefix)
for _, h := range m.befores {
if h(rw, req) {
return
}
}
m.Router.ServeHTTP(rw, req)
}
func GetDefaultListenInfo() (string, int) {
host := os.Getenv("HOST")
if len(host) == 0 {
host = "0.0.0.0"
}
port := com.StrTo(os.Getenv("PORT")).MustInt()
if port == 0 {
port = 4000
}
return host, port
}
// Run the http server. Listening on os.GetEnv("PORT") or 4000 by default.
func (m *Macaron) Run(args ...interface{}) {
host, port := GetDefaultListenInfo()
if len(args) == 1 {
switch arg := args[0].(type) {
case string:
host = arg
case int:
port = arg
}
} else if len(args) >= 2 {
if arg, ok := args[0].(string); ok {
host = arg
}
if arg, ok := args[1].(int); ok {
port = arg
}
}
addr := host + ":" + com.ToStr(port)
logger := m.Injector.GetVal(reflect.TypeOf(m.logger)).Interface().(*log.Logger)
logger.Printf("listening on %s (%s)\n", addr, Env)
logger.Fatalln(http.ListenAndServe(addr, m))
}
// SetURLPrefix sets URL prefix of router layer, so that it support suburl.
func (m *Macaron) SetURLPrefix(prefix string) {
m.urlPrefix = prefix
}
// ____ ____ .__ ___. .__
// \ \ / /____ _______|__|____ \_ |__ | | ____ ______
// \ Y /\__ \\_ __ \ \__ \ | __ \| | _/ __ \ / ___/
// \ / / __ \| | \/ |/ __ \| \_\ \ |_\ ___/ \___ \
// \___/ (____ /__| |__(____ /___ /____/\___ >____ >
// \/ \/ \/ \/ \/
const (
DEV = "development"
PROD = "production"
TEST = "test"
)
var (
// Env is the environment that Macaron is executing in.
// The MACARON_ENV is read on initialization to set this variable.
Env = DEV
// Path of work directory.
Root string
// Flash applies to current request.
FlashNow bool
// Configuration convention object.
cfg *ini.File
)
func setENV(e string) {
if len(e) > 0 {
Env = e
}
}
func init() {
setENV(os.Getenv("MACARON_ENV"))
var err error
Root, err = os.Getwd()
if err != nil {
panic("error getting work directory: " + err.Error())
}
}
// SetConfig sets data sources for configuration.
func SetConfig(source interface{}, others ...interface{}) (_ *ini.File, err error) {
cfg, err = ini.Load(source, others...)
return Config(), err
}
// Config returns configuration convention object.
// It returns an empty object if there is no one available.
func Config() *ini.File {
if cfg == nil {
return ini.Empty()
}
return cfg
}
+218
View File
@@ -0,0 +1,218 @@
// Copyright 2013 Martini Authors
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package macaron
import (
"net/http"
"net/http/httptest"
"os"
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
)
func Test_Version(t *testing.T) {
Convey("Get version", t, func() {
So(Version(), ShouldEqual, _VERSION)
})
}
func Test_New(t *testing.T) {
Convey("Initialize a new instance", t, func() {
So(New(), ShouldNotBeNil)
})
Convey("Just test that Run doesn't bomb", t, func() {
go New().Run()
time.Sleep(1 * time.Second)
os.Setenv("PORT", "4001")
go New().Run("0.0.0.0")
go New().Run(4002)
go New().Run("0.0.0.0", 4003)
})
}
func Test_Macaron_Before(t *testing.T) {
Convey("Register before handlers", t, func() {
m := New()
m.Before(func(rw http.ResponseWriter, req *http.Request) bool {
return false
})
m.Before(func(rw http.ResponseWriter, req *http.Request) bool {
return true
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
})
}
func Test_Macaron_ServeHTTP(t *testing.T) {
Convey("Serve HTTP requests", t, func() {
result := ""
m := New()
m.Use(func(c *Context) {
result += "foo"
c.Next()
result += "ban"
})
m.Use(func(c *Context) {
result += "bar"
c.Next()
result += "baz"
})
m.Get("/", func() {})
m.Action(func(res http.ResponseWriter, req *http.Request) {
result += "bat"
res.WriteHeader(http.StatusBadRequest)
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(result, ShouldEqual, "foobarbatbazban")
So(resp.Code, ShouldEqual, http.StatusBadRequest)
})
}
func Test_Macaron_Handlers(t *testing.T) {
Convey("Add custom handlers", t, func() {
result := ""
batman := func(c *Context) {
result += "batman!"
}
m := New()
m.Use(func(c *Context) {
result += "foo"
c.Next()
result += "ban"
})
m.Handlers(
batman,
batman,
batman,
)
Convey("Add not callable function", func() {
defer func() {
So(recover(), ShouldNotBeNil)
}()
m.Use("shit")
})
m.Get("/", func() {})
m.Action(func(res http.ResponseWriter, req *http.Request) {
result += "bat"
res.WriteHeader(http.StatusBadRequest)
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(result, ShouldEqual, "batman!batman!batman!bat")
So(resp.Code, ShouldEqual, http.StatusBadRequest)
})
}
func Test_Macaron_EarlyWrite(t *testing.T) {
Convey("Write early content to response", t, func() {
result := ""
m := New()
m.Use(func(res http.ResponseWriter) {
result += "foobar"
res.Write([]byte("Hello world"))
})
m.Use(func() {
result += "bat"
})
m.Get("/", func() {})
m.Action(func(res http.ResponseWriter) {
result += "baz"
res.WriteHeader(http.StatusBadRequest)
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(result, ShouldEqual, "foobar")
So(resp.Code, ShouldEqual, http.StatusOK)
})
}
func Test_Macaron_Written(t *testing.T) {
Convey("Written sign", t, func() {
resp := httptest.NewRecorder()
m := New()
m.Handlers(func(res http.ResponseWriter) {
res.WriteHeader(http.StatusOK)
})
ctx := m.createContext(resp, &http.Request{Method: "GET"})
So(ctx.Written(), ShouldBeFalse)
ctx.run()
So(ctx.Written(), ShouldBeTrue)
})
}
func Test_Macaron_Basic_NoRace(t *testing.T) {
Convey("Make sure no race between requests", t, func() {
m := New()
handlers := []Handler{func() {}, func() {}}
// Ensure append will not realloc to trigger the race condition
m.handlers = handlers[:1]
m.Get("/", func() {})
req, _ := http.NewRequest("GET", "/", nil)
for i := 0; i < 2; i++ {
go func() {
resp := httptest.NewRecorder()
m.ServeHTTP(resp, req)
}()
}
})
}
func Test_SetENV(t *testing.T) {
Convey("Get and save environment variable", t, func() {
tests := []struct {
in string
out string
}{
{"", "development"},
{"not_development", "not_development"},
}
for _, test := range tests {
setENV(test.in)
So(Env, ShouldEqual, test.out)
}
})
}
func Test_Config(t *testing.T) {
Convey("Set and get configuration object", t, func() {
So(Config(), ShouldNotBeNil)
cfg, err := SetConfig([]byte(""))
So(err, ShouldBeNil)
So(cfg, ShouldNotBeNil)
})
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

+163
View File
@@ -0,0 +1,163 @@
// Copyright 2013 Martini Authors
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package macaron
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"net/http"
"runtime"
"github.com/Unknwon/macaron/inject"
)
const (
panicHtml = `<html>
<head><title>PANIC: %s</title>
<meta charset="utf-8" />
<style type="text/css">
html, body {
font-family: "Roboto", sans-serif;
color: #333333;
background-color: #ea5343;
margin: 0px;
}
h1 {
color: #d04526;
background-color: #ffffff;
padding: 20px;
border-bottom: 1px dashed #2b3848;
}
pre {
margin: 20px;
padding: 20px;
border: 2px solid #2b3848;
background-color: #ffffff;
white-space: pre-wrap; /* css-3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
</style>
</head><body>
<h1>PANIC</h1>
<pre style="font-weight: bold;">%s</pre>
<pre>%s</pre>
</body>
</html>`
)
var (
dunno = []byte("???")
centerDot = []byte("·")
dot = []byte(".")
slash = []byte("/")
)
// stack returns a nicely formated stack frame, skipping skip frames
func stack(skip int) []byte {
buf := new(bytes.Buffer) // the returned data
// As we loop, we open files and read them. These variables record the currently
// loaded file.
var lines [][]byte
var lastFile string
for i := skip; ; i++ { // Skip the expected number of frames
pc, file, line, ok := runtime.Caller(i)
if !ok {
break
}
// Print this much at least. If we can't find the source, it won't show.
fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
if file != lastFile {
data, err := ioutil.ReadFile(file)
if err != nil {
continue
}
lines = bytes.Split(data, []byte{'\n'})
lastFile = file
}
fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
}
return buf.Bytes()
}
// source returns a space-trimmed slice of the n'th line.
func source(lines [][]byte, n int) []byte {
n-- // in stack trace, lines are 1-indexed but our array is 0-indexed
if n < 0 || n >= len(lines) {
return dunno
}
return bytes.TrimSpace(lines[n])
}
// function returns, if possible, the name of the function containing the PC.
func function(pc uintptr) []byte {
fn := runtime.FuncForPC(pc)
if fn == nil {
return dunno
}
name := []byte(fn.Name())
// The name includes the path name to the package, which is unnecessary
// since the file name is already included. Plus, it has center dots.
// That is, we see
// runtime/debug.*T·ptrmethod
// and want
// *T.ptrmethod
// Also the package path might contains dot (e.g. code.google.com/...),
// so first eliminate the path prefix
if lastslash := bytes.LastIndex(name, slash); lastslash >= 0 {
name = name[lastslash+1:]
}
if period := bytes.Index(name, dot); period >= 0 {
name = name[period+1:]
}
name = bytes.Replace(name, centerDot, dot, -1)
return name
}
// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
// While Martini is in development mode, Recovery will also output the panic as HTML.
func Recovery() Handler {
return func(c *Context, log *log.Logger) {
defer func() {
if err := recover(); err != nil {
stack := stack(3)
log.Printf("PANIC: %s\n%s", err, stack)
// Lookup the current responsewriter
val := c.GetVal(inject.InterfaceOf((*http.ResponseWriter)(nil)))
res := val.Interface().(http.ResponseWriter)
// respond with panic message while in development mode
var body []byte
if Env == DEV {
res.Header().Set("Content-Type", "text/html")
body = []byte(fmt.Sprintf(panicHtml, err, err, stack))
}
res.WriteHeader(http.StatusInternalServerError)
if nil != body {
res.Write(body)
}
}
}()
c.Next()
}
}
+74
View File
@@ -0,0 +1,74 @@
// Copyright 2013 Martini Authors
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package macaron
import (
"bytes"
"log"
"net/http"
"net/http/httptest"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func Test_Recovery(t *testing.T) {
Convey("Recovery from panic", t, func() {
buf := bytes.NewBufferString("")
setENV(DEV)
m := New()
m.Map(log.New(buf, "[Macaron] ", 0))
m.Use(func(res http.ResponseWriter, req *http.Request) {
res.Header().Set("Content-Type", "unpredictable")
})
m.Use(Recovery())
m.Use(func(res http.ResponseWriter, req *http.Request) {
panic("here is a panic!")
})
m.Get("/", func() {})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusInternalServerError)
So(resp.HeaderMap.Get("Content-Type"), ShouldEqual, "text/html")
So(buf.String(), ShouldNotBeEmpty)
})
Convey("Revocery panic to another response writer", t, func() {
resp := httptest.NewRecorder()
resp2 := httptest.NewRecorder()
setENV(DEV)
m := New()
m.Use(Recovery())
m.Use(func(c *Context) {
c.MapTo(resp2, (*http.ResponseWriter)(nil))
panic("here is a panic!")
})
m.Get("/", func() {})
req, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp2.Code, ShouldEqual, http.StatusInternalServerError)
So(resp2.HeaderMap.Get("Content-Type"), ShouldEqual, "text/html")
So(resp2.Body.Len(), ShouldBeGreaterThan, 0)
})
}
+624
View File
@@ -0,0 +1,624 @@
// Copyright 2013 Martini Authors
// Copyright 2013 oxtoacart
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package macaron
import (
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
"html/template"
"io"
"io/ioutil"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"sync"
"time"
"github.com/Unknwon/com"
)
// BufferPool implements a pool of bytes.Buffers in the form of a bounded channel.
type BufferPool struct {
c chan *bytes.Buffer
}
// NewBufferPool creates a new BufferPool bounded to the given size.
func NewBufferPool(size int) (bp *BufferPool) {
return &BufferPool{
c: make(chan *bytes.Buffer, size),
}
}
// Get gets a Buffer from the BufferPool, or creates a new one if none are available
// in the pool.
func (bp *BufferPool) Get() (b *bytes.Buffer) {
select {
case b = <-bp.c:
// reuse existing buffer
default:
// create new buffer
b = bytes.NewBuffer([]byte{})
}
return
}
// Put returns the given Buffer to the BufferPool.
func (bp *BufferPool) Put(b *bytes.Buffer) {
b.Reset()
bp.c <- b
}
const (
ContentType = "Content-Type"
ContentLength = "Content-Length"
ContentBinary = "application/octet-stream"
ContentJSON = "application/json"
ContentHTML = "text/html"
CONTENT_PLAIN = "text/plain"
ContentXHTML = "application/xhtml+xml"
ContentXML = "text/xml"
defaultCharset = "UTF-8"
)
var (
// Provides a temporary buffer to execute templates into and catch errors.
bufpool = NewBufferPool(64)
// Included helper functions for use when rendering html
helperFuncs = template.FuncMap{
"yield": func() (string, error) {
return "", fmt.Errorf("yield called with no layout defined")
},
"current": func() (string, error) {
return "", nil
},
}
)
type (
// TemplateFile represents a interface of template file that has name and can be read.
TemplateFile interface {
Name() string
Data() []byte
Ext() string
}
// TemplateFileSystem represents a interface of template file system that able to list all files.
TemplateFileSystem interface {
ListFiles() []TemplateFile
}
// Delims represents a set of Left and Right delimiters for HTML template rendering
Delims struct {
// Left delimiter, defaults to {{
Left string
// Right delimiter, defaults to }}
Right string
}
// RenderOptions represents a struct for specifying configuration options for the Render middleware.
RenderOptions struct {
// Directory to load templates. Default is "templates".
Directory string
// Layout template name. Will not render a layout if "". Default is to "".
Layout string
// Extensions to parse template files from. Defaults are [".tmpl", ".html"].
Extensions []string
// Funcs is a slice of FuncMaps to apply to the template upon compilation. This is useful for helper functions. Default is [].
Funcs []template.FuncMap
// Delims sets the action delimiters to the specified strings in the Delims struct.
Delims Delims
// Appends the given charset to the Content-Type header. Default is "UTF-8".
Charset string
// Outputs human readable JSON.
IndentJSON bool
// Outputs human readable XML.
IndentXML bool
// Prefixes the JSON output with the given bytes.
PrefixJSON []byte
// Prefixes the XML output with the given bytes.
PrefixXML []byte
// Allows changing of output to XHTML instead of HTML. Default is "text/html"
HTMLContentType string
// TemplateFileSystem is the interface for supporting any implmentation of template file system.
TemplateFileSystem
}
// HTMLOptions is a struct for overriding some rendering Options for specific HTML call
HTMLOptions struct {
// Layout template name. Overrides Options.Layout.
Layout string
}
Render interface {
http.ResponseWriter
RW() http.ResponseWriter
JSON(int, interface{})
JSONString(interface{}) (string, error)
RawData(int, []byte)
RenderData(int, []byte)
HTML(int, string, interface{}, ...HTMLOptions)
HTMLSet(int, string, string, interface{}, ...HTMLOptions)
HTMLSetString(string, string, interface{}, ...HTMLOptions) (string, error)
HTMLString(string, interface{}, ...HTMLOptions) (string, error)
HTMLSetBytes(string, string, interface{}, ...HTMLOptions) ([]byte, error)
HTMLBytes(string, interface{}, ...HTMLOptions) ([]byte, error)
XML(int, interface{})
Error(int, ...string)
Status(int)
SetTemplatePath(string, string)
HasTemplateSet(string) bool
}
)
// TplFile implements TemplateFile interface.
type TplFile struct {
name string
data []byte
ext string
}
// NewTplFile cerates new template file with given name and data.
func NewTplFile(name string, data []byte, ext string) *TplFile {
return &TplFile{name, data, ext}
}
func (f *TplFile) Name() string {
return f.name
}
func (f *TplFile) Data() []byte {
return f.data
}
func (f *TplFile) Ext() string {
return f.ext
}
// TplFileSystem implements TemplateFileSystem interface.
type TplFileSystem struct {
files []TemplateFile
}
// NewTemplateFileSystem creates new template file system with given options.
func NewTemplateFileSystem(opt RenderOptions, omitData bool) TplFileSystem {
fs := TplFileSystem{}
fs.files = make([]TemplateFile, 0, 10)
if err := filepath.Walk(opt.Directory, func(path string, info os.FileInfo, err error) error {
r, err := filepath.Rel(opt.Directory, path)
if err != nil {
return err
}
ext := GetExt(r)
for _, extension := range opt.Extensions {
if ext == extension {
var data []byte
if !omitData {
data, err = ioutil.ReadFile(path)
if err != nil {
return err
}
}
name := filepath.ToSlash((r[0 : len(r)-len(ext)]))
fs.files = append(fs.files, NewTplFile(name, data, ext))
break
}
}
return nil
}); err != nil {
panic("NewTemplateFileSystem: " + err.Error())
}
return fs
}
func (fs TplFileSystem) ListFiles() []TemplateFile {
return fs.files
}
func PrepareCharset(charset string) string {
if len(charset) != 0 {
return "; charset=" + charset
}
return "; charset=" + defaultCharset
}
func GetExt(s string) string {
index := strings.Index(s, ".")
if index == -1 {
return ""
}
return s[index:]
}
func compile(opt RenderOptions) *template.Template {
dir := opt.Directory
t := template.New(dir)
t.Delims(opt.Delims.Left, opt.Delims.Right)
// Parse an initial template in case we don't have any.
template.Must(t.Parse("Macaron"))
if opt.TemplateFileSystem == nil {
opt.TemplateFileSystem = NewTemplateFileSystem(opt, false)
}
for _, f := range opt.TemplateFileSystem.ListFiles() {
tmpl := t.New(f.Name())
for _, funcs := range opt.Funcs {
tmpl.Funcs(funcs)
}
// Bomb out if parse fails. We don't want any silent server starts.
template.Must(tmpl.Funcs(helperFuncs).Parse(string(f.Data())))
}
return t
}
const (
_DEFAULT_TPL_SET_NAME = "DEFAULT"
)
// templateSet represents a template set of type *template.Template.
type templateSet struct {
lock sync.RWMutex
sets map[string]*template.Template
dirs map[string]string
}
func newTemplateSet() *templateSet {
return &templateSet{
sets: make(map[string]*template.Template),
dirs: make(map[string]string),
}
}
func (ts *templateSet) Set(name string, opt *RenderOptions) *template.Template {
t := compile(*opt)
ts.lock.Lock()
defer ts.lock.Unlock()
ts.sets[name] = t
ts.dirs[name] = opt.Directory
return t
}
func (ts *templateSet) Get(name string) *template.Template {
ts.lock.RLock()
defer ts.lock.RUnlock()
return ts.sets[name]
}
func (ts *templateSet) GetDir(name string) string {
ts.lock.RLock()
defer ts.lock.RUnlock()
return ts.dirs[name]
}
func prepareOptions(options []RenderOptions) RenderOptions {
var opt RenderOptions
if len(options) > 0 {
opt = options[0]
}
// Defaults.
if len(opt.Directory) == 0 {
opt.Directory = "templates"
}
if len(opt.Extensions) == 0 {
opt.Extensions = []string{".tmpl", ".html"}
}
if len(opt.HTMLContentType) == 0 {
opt.HTMLContentType = ContentHTML
}
return opt
}
func ParseTplSet(tplSet string) (tplName string, tplDir string) {
tplSet = strings.TrimSpace(tplSet)
if len(tplSet) == 0 {
panic("empty template set argument")
}
infos := strings.Split(tplSet, ":")
if len(infos) == 1 {
tplDir = infos[0]
tplName = path.Base(tplDir)
} else {
tplName = infos[0]
tplDir = infos[1]
}
if !com.IsDir(tplDir) {
panic("template set path does not exist or is not a directory")
}
return tplName, tplDir
}
func renderHandler(opt RenderOptions, tplSets []string) Handler {
cs := PrepareCharset(opt.Charset)
ts := newTemplateSet()
ts.Set(_DEFAULT_TPL_SET_NAME, &opt)
var tmpOpt RenderOptions
for _, tplSet := range tplSets {
tplName, tplDir := ParseTplSet(tplSet)
tmpOpt = opt
tmpOpt.Directory = tplDir
ts.Set(tplName, &tmpOpt)
}
return func(ctx *Context) {
r := &TplRender{
ResponseWriter: ctx.Resp,
templateSet: ts,
Opt: &opt,
CompiledCharset: cs,
}
ctx.Data["TmplLoadTimes"] = func() string {
if r.startTime.IsZero() {
return ""
}
return fmt.Sprint(time.Since(r.startTime).Nanoseconds()/1e6) + "ms"
}
ctx.Render = r
ctx.MapTo(r, (*Render)(nil))
}
}
// Renderer is a Middleware that maps a macaron.Render service into the Macaron handler chain.
// An single variadic macaron.RenderOptions struct can be optionally provided to configure
// HTML rendering. The default directory for templates is "templates" and the default
// file extension is ".tmpl" and ".html".
//
// If MACARON_ENV is set to "" or "development" then templates will be recompiled on every request. For more performance, set the
// MACARON_ENV environment variable to "production".
func Renderer(options ...RenderOptions) Handler {
return renderHandler(prepareOptions(options), []string{})
}
func Renderers(options RenderOptions, tplSets ...string) Handler {
return renderHandler(prepareOptions([]RenderOptions{options}), tplSets)
}
type TplRender struct {
http.ResponseWriter
*templateSet
Opt *RenderOptions
CompiledCharset string
startTime time.Time
}
func (r *TplRender) RW() http.ResponseWriter {
return r.ResponseWriter
}
func (r *TplRender) JSON(status int, v interface{}) {
var (
result []byte
err error
)
if r.Opt.IndentJSON {
result, err = json.MarshalIndent(v, "", " ")
} else {
result, err = json.Marshal(v)
}
if err != nil {
http.Error(r, err.Error(), 500)
return
}
// json rendered fine, write out the result
r.Header().Set(ContentType, ContentJSON+r.CompiledCharset)
r.WriteHeader(status)
if len(r.Opt.PrefixJSON) > 0 {
r.Write(r.Opt.PrefixJSON)
}
r.Write(result)
}
func (r *TplRender) JSONString(v interface{}) (string, error) {
var result []byte
var err error
if r.Opt.IndentJSON {
result, err = json.MarshalIndent(v, "", " ")
} else {
result, err = json.Marshal(v)
}
if err != nil {
return "", err
}
return string(result), nil
}
func (r *TplRender) XML(status int, v interface{}) {
var result []byte
var err error
if r.Opt.IndentXML {
result, err = xml.MarshalIndent(v, "", " ")
} else {
result, err = xml.Marshal(v)
}
if err != nil {
http.Error(r, err.Error(), 500)
return
}
// XML rendered fine, write out the result
r.Header().Set(ContentType, ContentXML+r.CompiledCharset)
r.WriteHeader(status)
if len(r.Opt.PrefixXML) > 0 {
r.Write(r.Opt.PrefixXML)
}
r.Write(result)
}
func (r *TplRender) data(status int, contentType string, v []byte) {
if r.Header().Get(ContentType) == "" {
r.Header().Set(ContentType, contentType)
}
r.WriteHeader(status)
r.Write(v)
}
func (r *TplRender) RawData(status int, v []byte) {
r.data(status, ContentBinary, v)
}
func (r *TplRender) RenderData(status int, v []byte) {
r.data(status, CONTENT_PLAIN, v)
}
func (r *TplRender) execute(t *template.Template, name string, data interface{}) (*bytes.Buffer, error) {
buf := bufpool.Get()
return buf, t.ExecuteTemplate(buf, name, data)
}
func (r *TplRender) addYield(t *template.Template, tplName string, data interface{}) {
funcs := template.FuncMap{
"yield": func() (template.HTML, error) {
buf, err := r.execute(t, tplName, data)
// return safe html here since we are rendering our own template
return template.HTML(buf.String()), err
},
"current": func() (string, error) {
return tplName, nil
},
}
t.Funcs(funcs)
}
func (r *TplRender) renderBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (*bytes.Buffer, error) {
t := r.templateSet.Get(setName)
if Env == DEV {
opt := *r.Opt
opt.Directory = r.templateSet.GetDir(setName)
t = r.templateSet.Set(setName, &opt)
}
if t == nil {
return nil, fmt.Errorf("html/template: template \"%s\" is undefined", tplName)
}
opt := r.prepareHTMLOptions(htmlOpt)
if len(opt.Layout) > 0 {
r.addYield(t, tplName, data)
tplName = opt.Layout
}
out, err := r.execute(t, tplName, data)
if err != nil {
return nil, err
}
return out, nil
}
func (r *TplRender) renderHTML(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) {
r.startTime = time.Now()
out, err := r.renderBytes(setName, tplName, data, htmlOpt...)
if err != nil {
http.Error(r, err.Error(), http.StatusInternalServerError)
return
}
r.Header().Set(ContentType, r.Opt.HTMLContentType+r.CompiledCharset)
r.WriteHeader(status)
io.Copy(r, out)
bufpool.Put(out)
}
func (r *TplRender) HTML(status int, name string, data interface{}, htmlOpt ...HTMLOptions) {
r.renderHTML(status, _DEFAULT_TPL_SET_NAME, name, data, htmlOpt...)
}
func (r *TplRender) HTMLSet(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) {
r.renderHTML(status, setName, tplName, data, htmlOpt...)
}
func (r *TplRender) HTMLSetBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) {
out, err := r.renderBytes(setName, tplName, data, htmlOpt...)
if err != nil {
return []byte(""), err
}
return out.Bytes(), nil
}
func (r *TplRender) HTMLBytes(name string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) {
return r.HTMLSetBytes(_DEFAULT_TPL_SET_NAME, name, data, htmlOpt...)
}
func (r *TplRender) HTMLSetString(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (string, error) {
p, err := r.HTMLSetBytes(setName, tplName, data, htmlOpt...)
return string(p), err
}
func (r *TplRender) HTMLString(name string, data interface{}, htmlOpt ...HTMLOptions) (string, error) {
p, err := r.HTMLBytes(name, data, htmlOpt...)
return string(p), err
}
// Error writes the given HTTP status to the current ResponseWriter
func (r *TplRender) Error(status int, message ...string) {
r.WriteHeader(status)
if len(message) > 0 {
r.Write([]byte(message[0]))
}
}
func (r *TplRender) Status(status int) {
r.WriteHeader(status)
}
func (r *TplRender) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions {
if len(htmlOpt) > 0 {
return htmlOpt[0]
}
return HTMLOptions{
Layout: r.Opt.Layout,
}
}
func (r *TplRender) SetTemplatePath(setName, dir string) {
if len(setName) == 0 {
setName = _DEFAULT_TPL_SET_NAME
}
opt := *r.Opt
opt.Directory = dir
r.templateSet.Set(setName, &opt)
}
func (r *TplRender) HasTemplateSet(name string) bool {
return r.templateSet.Get(name) != nil
}
+581
View File
@@ -0,0 +1,581 @@
// Copyright 2013 Martini Authors
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package macaron
import (
"encoding/xml"
"html/template"
"net/http"
"net/http/httptest"
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
)
type Greeting struct {
One string `json:"one"`
Two string `json:"two"`
}
type GreetingXML struct {
XMLName xml.Name `xml:"greeting"`
One string `xml:"one,attr"`
Two string `xml:"two,attr"`
}
func Test_Render_JSON(t *testing.T) {
Convey("Render JSON", t, func() {
m := Classic()
m.Use(Renderer())
m.Get("/foobar", func(r Render) {
r.JSON(300, Greeting{"hello", "world"})
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/foobar", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusMultipleChoices)
So(resp.Header().Get(ContentType), ShouldEqual, ContentJSON+"; charset=UTF-8")
So(resp.Body.String(), ShouldEqual, `{"one":"hello","two":"world"}`)
})
Convey("Render JSON with prefix", t, func() {
m := Classic()
prefix := ")]}',\n"
m.Use(Renderer(RenderOptions{
PrefixJSON: []byte(prefix),
}))
m.Get("/foobar", func(r Render) {
r.JSON(300, Greeting{"hello", "world"})
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/foobar", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusMultipleChoices)
So(resp.Header().Get(ContentType), ShouldEqual, ContentJSON+"; charset=UTF-8")
So(resp.Body.String(), ShouldEqual, prefix+`{"one":"hello","two":"world"}`)
})
Convey("Render Indented JSON", t, func() {
m := Classic()
m.Use(Renderer(RenderOptions{
IndentJSON: true,
}))
m.Get("/foobar", func(r Render) {
r.JSON(300, Greeting{"hello", "world"})
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/foobar", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusMultipleChoices)
So(resp.Header().Get(ContentType), ShouldEqual, ContentJSON+"; charset=UTF-8")
So(resp.Body.String(), ShouldEqual, `{
"one": "hello",
"two": "world"
}`)
})
Convey("Render JSON and return string", t, func() {
m := Classic()
m.Use(Renderer())
m.Get("/foobar", func(r Render) {
result, err := r.JSONString(Greeting{"hello", "world"})
So(err, ShouldBeNil)
So(result, ShouldEqual, `{"one":"hello","two":"world"}`)
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/foobar", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
})
Convey("Render with charset JSON", t, func() {
m := Classic()
m.Use(Renderer(RenderOptions{
Charset: "foobar",
}))
m.Get("/foobar", func(r Render) {
r.JSON(300, Greeting{"hello", "world"})
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/foobar", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusMultipleChoices)
So(resp.Header().Get(ContentType), ShouldEqual, ContentJSON+"; charset=foobar")
So(resp.Body.String(), ShouldEqual, `{"one":"hello","two":"world"}`)
})
}
func Test_Render_XML(t *testing.T) {
Convey("Render XML", t, func() {
m := Classic()
m.Use(Renderer())
m.Get("/foobar", func(r Render) {
r.XML(300, GreetingXML{One: "hello", Two: "world"})
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/foobar", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusMultipleChoices)
So(resp.Header().Get(ContentType), ShouldEqual, ContentXML+"; charset=UTF-8")
So(resp.Body.String(), ShouldEqual, `<greeting one="hello" two="world"></greeting>`)
})
Convey("Render XML with prefix", t, func() {
m := Classic()
prefix := ")]}',\n"
m.Use(Renderer(RenderOptions{
PrefixXML: []byte(prefix),
}))
m.Get("/foobar", func(r Render) {
r.XML(300, GreetingXML{One: "hello", Two: "world"})
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/foobar", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusMultipleChoices)
So(resp.Header().Get(ContentType), ShouldEqual, ContentXML+"; charset=UTF-8")
So(resp.Body.String(), ShouldEqual, prefix+`<greeting one="hello" two="world"></greeting>`)
})
Convey("Render Indented XML", t, func() {
m := Classic()
m.Use(Renderer(RenderOptions{
IndentXML: true,
}))
m.Get("/foobar", func(r Render) {
r.XML(300, GreetingXML{One: "hello", Two: "world"})
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/foobar", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusMultipleChoices)
So(resp.Header().Get(ContentType), ShouldEqual, ContentXML+"; charset=UTF-8")
So(resp.Body.String(), ShouldEqual, `<greeting one="hello" two="world"></greeting>`)
})
}
func Test_Render_HTML(t *testing.T) {
Convey("Render HTML", t, func() {
m := Classic()
m.Use(Renderers(RenderOptions{
Directory: "fixtures/basic",
}, "fixtures/basic2"))
m.Get("/foobar", func(r Render) {
r.HTML(200, "hello", "jeremy")
r.SetTemplatePath("", "fixtures/basic2")
})
m.Get("/foobar2", func(r Render) {
if r.HasTemplateSet("basic2") {
r.HTMLSet(200, "basic2", "hello", "jeremy")
}
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/foobar", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusOK)
So(resp.Header().Get(ContentType), ShouldEqual, ContentHTML+"; charset=UTF-8")
So(resp.Body.String(), ShouldEqual, "<h1>Hello jeremy</h1>")
resp = httptest.NewRecorder()
req, err = http.NewRequest("GET", "/foobar2", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusOK)
So(resp.Header().Get(ContentType), ShouldEqual, ContentHTML+"; charset=UTF-8")
So(resp.Body.String(), ShouldEqual, "<h1>What's up, jeremy</h1>")
Convey("Change render templates path", func() {
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/foobar", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusOK)
So(resp.Header().Get(ContentType), ShouldEqual, ContentHTML+"; charset=UTF-8")
So(resp.Body.String(), ShouldEqual, "<h1>What's up, jeremy</h1>")
})
})
Convey("Render HTML and return string", t, func() {
m := Classic()
m.Use(Renderers(RenderOptions{
Directory: "fixtures/basic",
}, "basic2:fixtures/basic2"))
m.Get("/foobar", func(r Render) {
result, err := r.HTMLString("hello", "jeremy")
So(err, ShouldBeNil)
So(result, ShouldEqual, "<h1>Hello jeremy</h1>")
})
m.Get("/foobar2", func(r Render) {
result, err := r.HTMLSetString("basic2", "hello", "jeremy")
So(err, ShouldBeNil)
So(result, ShouldEqual, "<h1>What's up, jeremy</h1>")
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/foobar", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
resp = httptest.NewRecorder()
req, err = http.NewRequest("GET", "/foobar2", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
})
Convey("Render with nested HTML", t, func() {
m := Classic()
m.Use(Renderer(RenderOptions{
Directory: "fixtures/basic",
}))
m.Get("/foobar", func(r Render) {
r.HTML(200, "admin/index", "jeremy")
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/foobar", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusOK)
So(resp.Header().Get(ContentType), ShouldEqual, ContentHTML+"; charset=UTF-8")
So(resp.Body.String(), ShouldEqual, "<h1>Admin jeremy</h1>")
})
Convey("Render bad HTML", t, func() {
m := Classic()
m.Use(Renderer(RenderOptions{
Directory: "fixtures/basic",
}))
m.Get("/foobar", func(r Render) {
r.HTML(200, "nope", nil)
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/foobar", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusInternalServerError)
So(resp.Body.String(), ShouldEqual, "html/template: \"nope\" is undefined\n")
})
Convey("Invalid template set", t, func() {
Convey("Empty template set argument", func() {
defer func() {
So(recover(), ShouldNotBeNil)
}()
m := Classic()
m.Use(Renderers(RenderOptions{
Directory: "fixtures/basic",
}, ""))
})
Convey("Bad template set path", func() {
defer func() {
So(recover(), ShouldNotBeNil)
}()
m := Classic()
m.Use(Renderers(RenderOptions{
Directory: "fixtures/basic",
}, "404"))
})
})
}
func Test_Render_XHTML(t *testing.T) {
Convey("Render XHTML", t, func() {
m := Classic()
m.Use(Renderer(RenderOptions{
Directory: "fixtures/basic",
HTMLContentType: ContentXHTML,
}))
m.Get("/foobar", func(r Render) {
r.HTML(200, "hello", "jeremy")
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/foobar", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusOK)
So(resp.Header().Get(ContentType), ShouldEqual, ContentXHTML+"; charset=UTF-8")
So(resp.Body.String(), ShouldEqual, "<h1>Hello jeremy</h1>")
})
}
func Test_Render_Extensions(t *testing.T) {
Convey("Render with extensions", t, func() {
m := Classic()
m.Use(Renderer(RenderOptions{
Directory: "fixtures/basic",
Extensions: []string{".tmpl", ".html"},
}))
m.Get("/foobar", func(r Render) {
r.HTML(200, "hypertext", nil)
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/foobar", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusOK)
So(resp.Header().Get(ContentType), ShouldEqual, ContentHTML+"; charset=UTF-8")
So(resp.Body.String(), ShouldEqual, "Hypertext!")
})
}
func Test_Render_Funcs(t *testing.T) {
Convey("Render with functions", t, func() {
m := Classic()
m.Use(Renderer(RenderOptions{
Directory: "fixtures/custom_funcs",
Funcs: []template.FuncMap{
{
"myCustomFunc": func() string {
return "My custom function"
},
},
},
}))
m.Get("/foobar", func(r Render) {
r.HTML(200, "index", "jeremy")
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/foobar", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "My custom function")
})
}
func Test_Render_Layout(t *testing.T) {
Convey("Render with layout", t, func() {
m := Classic()
m.Use(Renderer(RenderOptions{
Directory: "fixtures/basic",
Layout: "layout",
}))
m.Get("/foobar", func(r Render) {
r.HTML(200, "content", "jeremy")
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/foobar", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "head<h1>jeremy</h1>foot")
})
Convey("Render with current layout", t, func() {
m := Classic()
m.Use(Renderer(RenderOptions{
Directory: "fixtures/basic",
Layout: "current_layout",
}))
m.Get("/foobar", func(r Render) {
r.HTML(200, "content", "jeremy")
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/foobar", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "content head<h1>jeremy</h1>content foot")
})
Convey("Render with override layout", t, func() {
m := Classic()
m.Use(Renderer(RenderOptions{
Directory: "fixtures/basic",
Layout: "layout",
}))
m.Get("/foobar", func(r Render) {
r.HTML(200, "content", "jeremy", HTMLOptions{
Layout: "another_layout",
})
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/foobar", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusOK)
So(resp.Header().Get(ContentType), ShouldEqual, ContentHTML+"; charset=UTF-8")
So(resp.Body.String(), ShouldEqual, "another head<h1>jeremy</h1>another foot")
})
}
func Test_Render_Delimiters(t *testing.T) {
Convey("Render with delimiters", t, func() {
m := Classic()
m.Use(Renderer(RenderOptions{
Delims: Delims{"{[{", "}]}"},
Directory: "fixtures/basic",
}))
m.Get("/foobar", func(r Render) {
r.HTML(200, "delims", "jeremy")
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/foobar", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusOK)
So(resp.Header().Get(ContentType), ShouldEqual, ContentHTML+"; charset=UTF-8")
So(resp.Body.String(), ShouldEqual, "<h1>Hello jeremy</h1>")
})
}
func Test_Render_BinaryData(t *testing.T) {
Convey("Render binary data", t, func() {
m := Classic()
m.Use(Renderer())
m.Get("/foobar", func(r Render) {
r.RawData(200, []byte("hello there"))
})
m.Get("/foobar2", func(r Render) {
r.RenderData(200, []byte("hello there"))
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/foobar", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusOK)
So(resp.Header().Get(ContentType), ShouldEqual, ContentBinary)
So(resp.Body.String(), ShouldEqual, "hello there")
resp = httptest.NewRecorder()
req, err = http.NewRequest("GET", "/foobar2", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusOK)
So(resp.Header().Get(ContentType), ShouldEqual, CONTENT_PLAIN)
So(resp.Body.String(), ShouldEqual, "hello there")
})
Convey("Render binary data with mime type", t, func() {
m := Classic()
m.Use(Renderer())
m.Get("/foobar", func(r Render) {
r.RW().Header().Set(ContentType, "image/jpeg")
r.RawData(200, []byte("..jpeg data.."))
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/foobar", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusOK)
So(resp.Header().Get(ContentType), ShouldEqual, "image/jpeg")
So(resp.Body.String(), ShouldEqual, "..jpeg data..")
})
}
func Test_Render_Status(t *testing.T) {
Convey("Render with status 204", t, func() {
resp := httptest.NewRecorder()
r := TplRender{resp, newTemplateSet(), &RenderOptions{}, "", time.Now()}
r.Status(204)
So(resp.Code, ShouldEqual, http.StatusNoContent)
})
Convey("Render with status 404", t, func() {
resp := httptest.NewRecorder()
r := TplRender{resp, newTemplateSet(), &RenderOptions{}, "", time.Now()}
r.Error(404)
So(resp.Code, ShouldEqual, http.StatusNotFound)
})
Convey("Render with status 500", t, func() {
resp := httptest.NewRecorder()
r := TplRender{resp, newTemplateSet(), &RenderOptions{}, "", time.Now()}
r.Error(500)
So(resp.Code, ShouldEqual, http.StatusInternalServerError)
})
}
func Test_Render_NoRace(t *testing.T) {
Convey("Make sure render has no race", t, func() {
m := Classic()
m.Use(Renderer(RenderOptions{
Directory: "fixtures/basic",
}))
m.Get("/foobar", func(r Render) {
r.HTML(200, "hello", "world")
})
done := make(chan bool)
doreq := func() {
resp := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/foobar", nil)
m.ServeHTTP(resp, req)
done <- true
}
// Run two requests to check there is no race condition
go doreq()
go doreq()
<-done
<-done
})
}
func Test_GetExt(t *testing.T) {
Convey("Get extension", t, func() {
So(GetExt("test"), ShouldBeBlank)
So(GetExt("test.tmpl"), ShouldEqual, ".tmpl")
So(GetExt("test.go.tmpl"), ShouldEqual, ".go.tmpl")
})
}
+111
View File
@@ -0,0 +1,111 @@
// Copyright 2013 Martini Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package macaron
import (
"bufio"
"fmt"
"net"
"net/http"
)
// ResponseWriter is a wrapper around http.ResponseWriter that provides extra information about
// the response. It is recommended that middleware handlers use this construct to wrap a responsewriter
// if the functionality calls for it.
type ResponseWriter interface {
http.ResponseWriter
http.Flusher
// Status returns the status code of the response or 0 if the response has not been written.
Status() int
// Written returns whether or not the ResponseWriter has been written.
Written() bool
// Size returns the size of the response body.
Size() int
// Before allows for a function to be called before the ResponseWriter has been written to. This is
// useful for setting headers or any other operations that must happen before a response has been written.
Before(BeforeFunc)
}
// BeforeFunc is a function that is called before the ResponseWriter has been written to.
type BeforeFunc func(ResponseWriter)
// NewResponseWriter creates a ResponseWriter that wraps an http.ResponseWriter
func NewResponseWriter(rw http.ResponseWriter) ResponseWriter {
return &responseWriter{rw, 0, 0, nil}
}
type responseWriter struct {
http.ResponseWriter
status int
size int
beforeFuncs []BeforeFunc
}
func (rw *responseWriter) WriteHeader(s int) {
rw.callBefore()
rw.ResponseWriter.WriteHeader(s)
rw.status = s
}
func (rw *responseWriter) Write(b []byte) (int, error) {
if !rw.Written() {
// The status will be StatusOK if WriteHeader has not been called yet
rw.WriteHeader(http.StatusOK)
}
size, err := rw.ResponseWriter.Write(b)
rw.size += size
return size, err
}
func (rw *responseWriter) Status() int {
return rw.status
}
func (rw *responseWriter) Size() int {
return rw.size
}
func (rw *responseWriter) Written() bool {
return rw.status != 0
}
func (rw *responseWriter) Before(before BeforeFunc) {
rw.beforeFuncs = append(rw.beforeFuncs, before)
}
func (rw *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
hijacker, ok := rw.ResponseWriter.(http.Hijacker)
if !ok {
return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface")
}
return hijacker.Hijack()
}
func (rw *responseWriter) CloseNotify() <-chan bool {
return rw.ResponseWriter.(http.CloseNotifier).CloseNotify()
}
func (rw *responseWriter) callBefore() {
for i := len(rw.beforeFuncs) - 1; i >= 0; i-- {
rw.beforeFuncs[i](rw)
}
}
func (rw *responseWriter) Flush() {
flusher, ok := rw.ResponseWriter.(http.Flusher)
if ok {
flusher.Flush()
}
}
@@ -0,0 +1,188 @@
// Copyright 2013 Martini Authors
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package macaron
import (
"bufio"
"io"
"net"
"net/http"
"net/http/httptest"
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
)
type closeNotifyingRecorder struct {
*httptest.ResponseRecorder
closed chan bool
}
func newCloseNotifyingRecorder() *closeNotifyingRecorder {
return &closeNotifyingRecorder{
httptest.NewRecorder(),
make(chan bool, 1),
}
}
func (c *closeNotifyingRecorder) close() {
c.closed <- true
}
func (c *closeNotifyingRecorder) CloseNotify() <-chan bool {
return c.closed
}
type hijackableResponse struct {
Hijacked bool
}
func newHijackableResponse() *hijackableResponse {
return &hijackableResponse{}
}
func (h *hijackableResponse) Header() http.Header { return nil }
func (h *hijackableResponse) Write(buf []byte) (int, error) { return 0, nil }
func (h *hijackableResponse) WriteHeader(code int) {}
func (h *hijackableResponse) Flush() {}
func (h *hijackableResponse) Hijack() (net.Conn, *bufio.ReadWriter, error) {
h.Hijacked = true
return nil, nil, nil
}
func Test_ResponseWriter(t *testing.T) {
Convey("Write string to response writer", t, func() {
resp := httptest.NewRecorder()
rw := NewResponseWriter(resp)
rw.Write([]byte("Hello world"))
So(resp.Code, ShouldEqual, rw.Status())
So(resp.Body.String(), ShouldEqual, "Hello world")
So(rw.Status(), ShouldEqual, http.StatusOK)
So(rw.Size(), ShouldEqual, 11)
So(rw.Written(), ShouldBeTrue)
})
Convey("Write strings to response writer", t, func() {
resp := httptest.NewRecorder()
rw := NewResponseWriter(resp)
rw.Write([]byte("Hello world"))
rw.Write([]byte("foo bar bat baz"))
So(resp.Code, ShouldEqual, rw.Status())
So(resp.Body.String(), ShouldEqual, "Hello worldfoo bar bat baz")
So(rw.Status(), ShouldEqual, http.StatusOK)
So(rw.Size(), ShouldEqual, 26)
So(rw.Written(), ShouldBeTrue)
})
Convey("Write header to response writer", t, func() {
resp := httptest.NewRecorder()
rw := NewResponseWriter(resp)
rw.WriteHeader(http.StatusNotFound)
So(resp.Code, ShouldEqual, rw.Status())
So(resp.Body.String(), ShouldBeBlank)
So(rw.Status(), ShouldEqual, http.StatusNotFound)
So(rw.Size(), ShouldEqual, 0)
})
Convey("Write before response write", t, func() {
result := ""
resp := httptest.NewRecorder()
rw := NewResponseWriter(resp)
rw.Before(func(ResponseWriter) {
result += "foo"
})
rw.Before(func(ResponseWriter) {
result += "bar"
})
rw.WriteHeader(http.StatusNotFound)
So(resp.Code, ShouldEqual, rw.Status())
So(resp.Body.String(), ShouldBeBlank)
So(rw.Status(), ShouldEqual, http.StatusNotFound)
So(rw.Size(), ShouldEqual, 0)
So(result, ShouldEqual, "barfoo")
})
Convey("Response writer with Hijack", t, func() {
hijackable := newHijackableResponse()
rw := NewResponseWriter(hijackable)
hijacker, ok := rw.(http.Hijacker)
So(ok, ShouldBeTrue)
_, _, err := hijacker.Hijack()
So(err, ShouldBeNil)
So(hijackable.Hijacked, ShouldBeTrue)
})
Convey("Response writer with bad Hijack", t, func() {
hijackable := new(http.ResponseWriter)
rw := NewResponseWriter(*hijackable)
hijacker, ok := rw.(http.Hijacker)
So(ok, ShouldBeTrue)
_, _, err := hijacker.Hijack()
So(err, ShouldNotBeNil)
})
Convey("Response writer with close notify", t, func() {
resp := newCloseNotifyingRecorder()
rw := NewResponseWriter(resp)
closed := false
notifier := rw.(http.CloseNotifier).CloseNotify()
resp.close()
select {
case <-notifier:
closed = true
case <-time.After(time.Second):
}
So(closed, ShouldBeTrue)
})
Convey("Response writer with flusher", t, func() {
resp := httptest.NewRecorder()
rw := NewResponseWriter(resp)
_, ok := rw.(http.Flusher)
So(ok, ShouldBeTrue)
})
Convey("Response writer with flusher handler", t, func() {
m := Classic()
m.Get("/events", func(w http.ResponseWriter, r *http.Request) {
f, ok := w.(http.Flusher)
So(ok, ShouldBeTrue)
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
for i := 0; i < 2; i++ {
time.Sleep(10 * time.Millisecond)
io.WriteString(w, "data: Hello\n\n")
f.Flush()
}
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/events", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusOK)
So(resp.Body.String(), ShouldEqual, "data: Hello\n\ndata: Hello\n\n")
})
}
+59
View File
@@ -0,0 +1,59 @@
// Copyright 2013 Martini Authors
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package macaron
import (
"net/http"
"reflect"
"github.com/Unknwon/macaron/inject"
)
// ReturnHandler is a service that Martini provides that is called
// when a route handler returns something. The ReturnHandler is
// responsible for writing to the ResponseWriter based on the values
// that are passed into this function.
type ReturnHandler func(*Context, []reflect.Value)
func canDeref(val reflect.Value) bool {
return val.Kind() == reflect.Interface || val.Kind() == reflect.Ptr
}
func isByteSlice(val reflect.Value) bool {
return val.Kind() == reflect.Slice && val.Type().Elem().Kind() == reflect.Uint8
}
func defaultReturnHandler() ReturnHandler {
return func(ctx *Context, vals []reflect.Value) {
rv := ctx.GetVal(inject.InterfaceOf((*http.ResponseWriter)(nil)))
res := rv.Interface().(http.ResponseWriter)
var respVal reflect.Value
if len(vals) > 1 && vals[0].Kind() == reflect.Int {
res.WriteHeader(int(vals[0].Int()))
respVal = vals[1]
} else if len(vals) > 0 {
respVal = vals[0]
}
if canDeref(respVal) {
respVal = respVal.Elem()
}
if isByteSlice(respVal) {
res.Write(respVal.Bytes())
} else {
res.Write([]byte(respVal.String()))
}
}
}
@@ -0,0 +1,69 @@
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package macaron
import (
"net/http"
"net/http/httptest"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func Test_Return_Handler(t *testing.T) {
Convey("Return with status and body", t, func() {
m := Classic()
m.Get("/", func() (int, string) {
return 418, "i'm a teapot"
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusTeapot)
So(resp.Body.String(), ShouldEqual, "i'm a teapot")
})
Convey("Return with pointer", t, func() {
m := Classic()
m.Get("/", func() *string {
str := "hello world"
return &str
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "hello world")
})
Convey("Return with byte slice", t, func() {
m := Classic()
m.Get("/", func() []byte {
return []byte("hello world")
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "hello world")
})
}
+300
View File
@@ -0,0 +1,300 @@
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package macaron
import (
"net/http"
"strings"
"sync"
"github.com/Unknwon/com"
)
var (
// Known HTTP methods.
_HTTP_METHODS = map[string]bool{
"GET": true,
"POST": true,
"PUT": true,
"DELETE": true,
"PATCH": true,
"OPTIONS": true,
"HEAD": true,
}
)
// routeMap represents a thread-safe map for route tree.
type routeMap struct {
lock sync.RWMutex
routes map[string]map[string]bool
}
// NewRouteMap initializes and returns a new routeMap.
func NewRouteMap() *routeMap {
rm := &routeMap{
routes: make(map[string]map[string]bool),
}
for m := range _HTTP_METHODS {
rm.routes[m] = make(map[string]bool)
}
return rm
}
// isExist returns true if a route has been registered.
func (rm *routeMap) isExist(method, pattern string) bool {
rm.lock.RLock()
defer rm.lock.RUnlock()
return rm.routes[method][pattern]
}
// add adds new route to route tree map.
func (rm *routeMap) add(method, pattern string) {
rm.lock.Lock()
defer rm.lock.Unlock()
rm.routes[method][pattern] = true
}
type group struct {
pattern string
handlers []Handler
}
// Router represents a Macaron router layer.
type Router struct {
m *Macaron
routers map[string]*Tree
*routeMap
groups []group
notFound http.HandlerFunc
}
func NewRouter() *Router {
return &Router{
routers: make(map[string]*Tree),
routeMap: NewRouteMap(),
}
}
type Params map[string]string
// Handle is a function that can be registered to a route to handle HTTP requests.
// Like http.HandlerFunc, but has a third parameter for the values of wildcards (variables).
type Handle func(http.ResponseWriter, *http.Request, Params)
// handle adds new route to the router tree.
func (r *Router) handle(method, pattern string, handle Handle) {
method = strings.ToUpper(method)
// Prevent duplicate routes.
if r.isExist(method, pattern) {
return
}
// Validate HTTP methods.
if !_HTTP_METHODS[method] && method != "*" {
panic("unknown HTTP method: " + method)
}
// Generate methods need register.
methods := make(map[string]bool)
if method == "*" {
for m := range _HTTP_METHODS {
methods[m] = true
}
} else {
methods[method] = true
}
// Add to router tree.
for m := range methods {
if t, ok := r.routers[m]; ok {
t.AddRouter(pattern, handle)
} else {
t := NewTree()
t.AddRouter(pattern, handle)
r.routers[m] = t
}
r.add(m, pattern)
}
}
// Handle registers a new request handle with the given pattern, method and handlers.
func (r *Router) Handle(method string, pattern string, handlers []Handler) {
if len(r.groups) > 0 {
groupPattern := ""
h := make([]Handler, 0)
for _, g := range r.groups {
groupPattern += g.pattern
h = append(h, g.handlers...)
}
pattern = groupPattern + pattern
h = append(h, handlers...)
handlers = h
}
validateHandlers(handlers)
r.handle(method, pattern, func(resp http.ResponseWriter, req *http.Request, params Params) {
c := r.m.createContext(resp, req)
c.params = params
c.handlers = make([]Handler, 0, len(r.m.handlers)+len(handlers))
c.handlers = append(c.handlers, r.m.handlers...)
c.handlers = append(c.handlers, handlers...)
c.run()
})
}
func (r *Router) Group(pattern string, fn func(), h ...Handler) {
r.groups = append(r.groups, group{pattern, h})
fn()
r.groups = r.groups[:len(r.groups)-1]
}
// Get is a shortcut for r.Handle("GET", pattern, handlers)
func (r *Router) Get(pattern string, h ...Handler) {
r.Handle("GET", pattern, h)
}
// Patch is a shortcut for r.Handle("PATCH", pattern, handlers)
func (r *Router) Patch(pattern string, h ...Handler) {
r.Handle("PATCH", pattern, h)
}
// Post is a shortcut for r.Handle("POST", pattern, handlers)
func (r *Router) Post(pattern string, h ...Handler) {
r.Handle("POST", pattern, h)
}
// Put is a shortcut for r.Handle("PUT", pattern, handlers)
func (r *Router) Put(pattern string, h ...Handler) {
r.Handle("PUT", pattern, h)
}
// Delete is a shortcut for r.Handle("DELETE", pattern, handlers)
func (r *Router) Delete(pattern string, h ...Handler) {
r.Handle("DELETE", pattern, h)
}
// Options is a shortcut for r.Handle("OPTIONS", pattern, handlers)
func (r *Router) Options(pattern string, h ...Handler) {
r.Handle("OPTIONS", pattern, h)
}
// Head is a shortcut for r.Handle("HEAD", pattern, handlers)
func (r *Router) Head(pattern string, h ...Handler) {
r.Handle("HEAD", pattern, h)
}
// Any is a shortcut for r.Handle("*", pattern, handlers)
func (r *Router) Any(pattern string, h ...Handler) {
r.Handle("*", pattern, h)
}
// Route is a shortcut for same handlers but different HTTP methods.
//
// Example:
// m.Route("/", "GET,POST", h)
func (r *Router) Route(pattern, methods string, h ...Handler) {
for _, m := range strings.Split(methods, ",") {
r.Handle(strings.TrimSpace(m), pattern, h)
}
}
// Combo returns a combo router.
func (r *Router) Combo(pattern string, h ...Handler) *ComboRouter {
return &ComboRouter{r, pattern, h, map[string]bool{}}
}
// Configurable http.HandlerFunc which is called when no matching route is
// found. If it is not set, http.NotFound is used.
// Be sure to set 404 response code in your handler.
func (r *Router) NotFound(handlers ...Handler) {
r.notFound = func(rw http.ResponseWriter, req *http.Request) {
c := r.m.createContext(rw, req)
c.handlers = append(r.m.handlers, handlers...)
c.run()
}
}
func (r *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if t, ok := r.routers[req.Method]; ok {
h, p := t.Match(req.URL.Path)
if h != nil {
if splat, ok := p[":splat"]; ok {
p["*"] = p[":splat"] // Better name.
splatlist := strings.Split(splat, "/")
for k, v := range splatlist {
p[com.ToStr(k)] = v
}
}
h(rw, req, p)
return
}
}
r.notFound(rw, req)
}
// ComboRouter represents a combo router.
type ComboRouter struct {
router *Router
pattern string
handlers []Handler
methods map[string]bool // Registered methods.
}
func (cr *ComboRouter) checkMethod(name string) {
if cr.methods[name] {
panic("method '" + name + "' has already been registered")
}
cr.methods[name] = true
}
func (cr *ComboRouter) route(fn func(string, ...Handler), method string, h ...Handler) *ComboRouter {
cr.checkMethod(method)
fn(cr.pattern, append(cr.handlers, h...)...)
return cr
}
func (cr *ComboRouter) Get(h ...Handler) *ComboRouter {
return cr.route(cr.router.Get, "GET", h...)
}
func (cr *ComboRouter) Patch(h ...Handler) *ComboRouter {
return cr.route(cr.router.Patch, "PATCH", h...)
}
func (cr *ComboRouter) Post(h ...Handler) *ComboRouter {
return cr.route(cr.router.Post, "POST", h...)
}
func (cr *ComboRouter) Put(h ...Handler) *ComboRouter {
return cr.route(cr.router.Put, "PUT", h...)
}
func (cr *ComboRouter) Delete(h ...Handler) *ComboRouter {
return cr.route(cr.router.Delete, "DELETE", h...)
}
func (cr *ComboRouter) Options(h ...Handler) *ComboRouter {
return cr.route(cr.router.Options, "OPTIONS", h...)
}
func (cr *ComboRouter) Head(h ...Handler) *ComboRouter {
return cr.route(cr.router.Head, "HEAD", h...)
}
+199
View File
@@ -0,0 +1,199 @@
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package macaron
import (
"net/http"
"net/http/httptest"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func Test_Router_Handle(t *testing.T) {
Convey("Register all HTTP methods routes", t, func() {
m := Classic()
m.Get("/get", func() string {
return "GET"
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/get", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "GET")
m.Patch("/patch", func() string {
return "PATCH"
})
resp = httptest.NewRecorder()
req, err = http.NewRequest("PATCH", "/patch", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "PATCH")
m.Post("/post", func() string {
return "POST"
})
resp = httptest.NewRecorder()
req, err = http.NewRequest("POST", "/post", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "POST")
m.Put("/put", func() string {
return "PUT"
})
resp = httptest.NewRecorder()
req, err = http.NewRequest("PUT", "/put", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "PUT")
m.Delete("/delete", func() string {
return "DELETE"
})
resp = httptest.NewRecorder()
req, err = http.NewRequest("DELETE", "/delete", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "DELETE")
m.Options("/options", func() string {
return "OPTIONS"
})
resp = httptest.NewRecorder()
req, err = http.NewRequest("OPTIONS", "/options", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "OPTIONS")
m.Head("/head", func() string {
return "HEAD"
})
resp = httptest.NewRecorder()
req, err = http.NewRequest("HEAD", "/head", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "HEAD")
m.Any("/any", func() string {
return "ANY"
})
resp = httptest.NewRecorder()
req, err = http.NewRequest("GET", "/any", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "ANY")
m.Route("/route", "GET,POST", func() string {
return "ROUTE"
})
resp = httptest.NewRecorder()
req, err = http.NewRequest("POST", "/route", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "ROUTE")
})
Convey("Register all HTTP methods routes with combo", t, func() {
m := Classic()
m.SetURLPrefix("/prefix")
m.Use(Renderer())
m.Combo("/", func(ctx *Context) {
ctx.Data["prefix"] = "Prefix_"
}).
Get(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "GET" }).
Patch(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "PATCH" }).
Post(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "POST" }).
Put(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "PUT" }).
Delete(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "DELETE" }).
Options(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "OPTIONS" }).
Head(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "HEAD" })
for name := range _HTTP_METHODS {
resp := httptest.NewRecorder()
req, err := http.NewRequest(name, "/", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "Prefix_"+name)
}
defer func() {
So(recover(), ShouldNotBeNil)
}()
m.Combo("/").Get(func() {}).Get(nil)
})
Convey("Register duplicated routes", t, func() {
r := NewRouter()
r.Get("/")
r.Get("/")
})
Convey("Register invalid HTTP method", t, func() {
defer func() {
So(recover(), ShouldNotBeNil)
}()
r := NewRouter()
r.Handle("404", "/", nil)
})
}
func Test_Router_Group(t *testing.T) {
Convey("Register route group", t, func() {
m := Classic()
m.Group("/api", func() {
m.Group("/v1", func() {
m.Get("/list", func() string {
return "Well done!"
})
})
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/api/v1/list", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "Well done!")
})
}
func Test_Router_NotFound(t *testing.T) {
Convey("Custom not found handler", t, func() {
m := Classic()
m.Get("/", func() {})
m.NotFound(func() string {
return "Custom not found"
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/404", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "Custom not found")
})
}
func Test_Router_splat(t *testing.T) {
Convey("Register router with glob", t, func() {
m := Classic()
m.Get("/*", func(ctx *Context) string {
return ctx.Params("*")
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/hahaha", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Body.String(), ShouldEqual, "hahaha")
})
}
+205
View File
@@ -0,0 +1,205 @@
// Copyright 2013 Martini Authors
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package macaron
import (
"log"
"net/http"
"path"
"path/filepath"
"strings"
"sync"
)
// StaticOptions is a struct for specifying configuration options for the macaron.Static middleware.
type StaticOptions struct {
// Prefix is the optional prefix used to serve the static directory content
Prefix string
// SkipLogging will disable [Static] log messages when a static file is served.
SkipLogging bool
// IndexFile defines which file to serve as index if it exists.
IndexFile string
// Expires defines which user-defined function to use for producing a HTTP Expires Header
// https://developers.google.com/speed/docs/insights/LeverageBrowserCaching
Expires func() string
// FileSystem is the interface for supporting any implmentation of file system.
FileSystem http.FileSystem
}
// FIXME: to be deleted.
type staticMap struct {
lock sync.RWMutex
data map[string]*http.Dir
}
func (sm *staticMap) Set(dir *http.Dir) {
sm.lock.Lock()
defer sm.lock.Unlock()
sm.data[string(*dir)] = dir
}
func (sm *staticMap) Get(name string) *http.Dir {
sm.lock.RLock()
defer sm.lock.RUnlock()
return sm.data[name]
}
func (sm *staticMap) Delete(name string) {
sm.lock.Lock()
defer sm.lock.Unlock()
delete(sm.data, name)
}
var statics = staticMap{sync.RWMutex{}, map[string]*http.Dir{}}
// staticFileSystem implements http.FileSystem interface.
type staticFileSystem struct {
dir *http.Dir
}
func newStaticFileSystem(directory string) staticFileSystem {
if !filepath.IsAbs(directory) {
directory = filepath.Join(Root, directory)
}
dir := http.Dir(directory)
statics.Set(&dir)
return staticFileSystem{&dir}
}
func (fs staticFileSystem) Open(name string) (http.File, error) {
return fs.dir.Open(name)
}
func prepareStaticOption(dir string, opt StaticOptions) StaticOptions {
// Defaults
if len(opt.IndexFile) == 0 {
opt.IndexFile = "index.html"
}
// Normalize the prefix if provided
if opt.Prefix != "" {
// Ensure we have a leading '/'
if opt.Prefix[0] != '/' {
opt.Prefix = "/" + opt.Prefix
}
// Remove any trailing '/'
opt.Prefix = strings.TrimRight(opt.Prefix, "/")
}
if opt.FileSystem == nil {
opt.FileSystem = newStaticFileSystem(dir)
}
return opt
}
func prepareStaticOptions(dir string, options []StaticOptions) StaticOptions {
var opt StaticOptions
if len(options) > 0 {
opt = options[0]
}
return prepareStaticOption(dir, opt)
}
func staticHandler(ctx *Context, log *log.Logger, opt StaticOptions) bool {
if ctx.Req.Method != "GET" && ctx.Req.Method != "HEAD" {
return false
}
file := ctx.Req.URL.Path
// if we have a prefix, filter requests by stripping the prefix
if opt.Prefix != "" {
if !strings.HasPrefix(file, opt.Prefix) {
return false
}
file = file[len(opt.Prefix):]
if file != "" && file[0] != '/' {
return false
}
}
f, err := opt.FileSystem.Open(file)
if err != nil {
return false
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return true // File exists but fail to open.
}
// Try to serve index file
if fi.IsDir() {
// Redirect if missing trailing slash.
if !strings.HasSuffix(ctx.Req.URL.Path, "/") {
http.Redirect(ctx.Resp, ctx.Req.Request, ctx.Req.URL.Path+"/", http.StatusFound)
return true
}
file = path.Join(file, opt.IndexFile)
f, err = opt.FileSystem.Open(file)
if err != nil {
return false // Discard error.
}
defer f.Close()
fi, err = f.Stat()
if err != nil || fi.IsDir() {
return true
}
}
if !opt.SkipLogging {
log.Println("[Static] Serving " + file)
}
// Add an Expires header to the static content
if opt.Expires != nil {
ctx.Resp.Header().Set("Expires", opt.Expires())
}
http.ServeContent(ctx.Resp, ctx.Req.Request, file, fi.ModTime(), f)
return true
}
// Static returns a middleware handler that serves static files in the given directory.
func Static(directory string, staticOpt ...StaticOptions) Handler {
opt := prepareStaticOptions(directory, staticOpt)
return func(ctx *Context, log *log.Logger) {
staticHandler(ctx, log, opt)
}
}
// Statics registers multiple static middleware handlers all at once.
func Statics(opt StaticOptions, dirs ...string) Handler {
if len(dirs) == 0 {
panic("no static directory is given")
}
opts := make([]StaticOptions, len(dirs))
for i := range dirs {
opts[i] = prepareStaticOption(dirs[i], opt)
}
return func(ctx *Context, log *log.Logger) {
for i := range opts {
if staticHandler(ctx, log, opts[i]) {
return
}
}
}
}
+246
View File
@@ -0,0 +1,246 @@
// Copyright 2013 Martini Authors
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package macaron
import (
"bytes"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"path"
"strings"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
var currentRoot, _ = os.Getwd()
func Test_Static(t *testing.T) {
Convey("Serve static files", t, func() {
m := New()
m.Use(Static("./"))
resp := httptest.NewRecorder()
resp.Body = new(bytes.Buffer)
req, err := http.NewRequest("GET", "http://localhost:4000/macaron.go", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusOK)
So(resp.Header().Get("Expires"), ShouldBeBlank)
So(resp.Body.Len(), ShouldBeGreaterThan, 0)
Convey("Change static path", func() {
m.Get("/", func(ctx *Context) {
ctx.ChangeStaticPath("./", "inject")
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
resp = httptest.NewRecorder()
resp.Body = new(bytes.Buffer)
req, err = http.NewRequest("GET", "http://localhost:4000/inject.go", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusOK)
So(resp.Header().Get("Expires"), ShouldBeBlank)
So(resp.Body.Len(), ShouldBeGreaterThan, 0)
})
})
Convey("Serve static files with local path", t, func() {
Root = os.TempDir()
f, err := ioutil.TempFile(Root, "static_content")
So(err, ShouldBeNil)
f.WriteString("Expected Content")
f.Close()
m := New()
m.Use(Static("."))
resp := httptest.NewRecorder()
resp.Body = new(bytes.Buffer)
req, err := http.NewRequest("GET", "http://localhost:4000/"+path.Base(strings.Replace(f.Name(), "\\", "/", -1)), nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusOK)
So(resp.Header().Get("Expires"), ShouldBeBlank)
So(resp.Body.String(), ShouldEqual, "Expected Content")
})
Convey("Serve static files with head", t, func() {
m := New()
m.Use(Static(currentRoot))
resp := httptest.NewRecorder()
resp.Body = new(bytes.Buffer)
req, err := http.NewRequest("HEAD", "http://localhost:4000/macaron.go", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusOK)
So(resp.Body.Len(), ShouldEqual, 0)
})
Convey("Serve static files as post", t, func() {
m := New()
m.Use(Static(currentRoot))
resp := httptest.NewRecorder()
req, err := http.NewRequest("POST", "http://localhost:4000/macaron.go", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusNotFound)
})
Convey("Serve static files with bad directory", t, func() {
m := Classic()
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "http://localhost:4000/macaron.go", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldNotEqual, http.StatusOK)
})
}
func Test_Static_Options(t *testing.T) {
Convey("Serve static files with options logging", t, func() {
var buf bytes.Buffer
m := NewWithLogger(&buf)
opt := StaticOptions{}
m.Use(Static(currentRoot, opt))
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "http://localhost:4000/macaron.go", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusOK)
So(buf.String(), ShouldEqual, "[Macaron] [Static] Serving /macaron.go\n")
// Not disable logging.
m.Handlers()
buf.Reset()
opt.SkipLogging = true
m.Use(Static(currentRoot, opt))
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusOK)
So(buf.Len(), ShouldEqual, 0)
})
Convey("Serve static files with options serve index", t, func() {
var buf bytes.Buffer
m := NewWithLogger(&buf)
opt := StaticOptions{IndexFile: "macaron.go"}
m.Use(Static(currentRoot, opt))
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "http://localhost:4000/", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusOK)
So(buf.String(), ShouldEqual, "[Macaron] [Static] Serving /macaron.go\n")
})
Convey("Serve static files with options prefix", t, func() {
var buf bytes.Buffer
m := NewWithLogger(&buf)
opt := StaticOptions{Prefix: "public"}
m.Use(Static(currentRoot, opt))
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "http://localhost:4000/public/macaron.go", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusOK)
So(buf.String(), ShouldEqual, "[Macaron] [Static] Serving /macaron.go\n")
})
Convey("Serve static files with options expires", t, func() {
var buf bytes.Buffer
m := NewWithLogger(&buf)
opt := StaticOptions{Expires: func() string { return "46" }}
m.Use(Static(currentRoot, opt))
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "http://localhost:4000/macaron.go", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Header().Get("Expires"), ShouldEqual, "46")
})
}
func Test_Static_Redirect(t *testing.T) {
Convey("Serve static files with redirect", t, func() {
m := New()
m.Use(Static(currentRoot, StaticOptions{Prefix: "/public"}))
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "http://localhost:4000/public", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusFound)
So(resp.Header().Get("Location"), ShouldEqual, "/public/")
})
}
func Test_Statics(t *testing.T) {
Convey("Serve multiple static routers", t, func() {
Convey("Register empty directory", func() {
defer func() {
So(recover(), ShouldNotBeNil)
}()
m := New()
m.Use(Statics(StaticOptions{}))
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "http://localhost:4000/", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
})
Convey("Serve normally", func() {
var buf bytes.Buffer
m := NewWithLogger(&buf)
m.Use(Statics(StaticOptions{}, currentRoot, currentRoot+"/inject"))
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "http://localhost:4000/macaron.go", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusOK)
So(buf.String(), ShouldEqual, "[Macaron] [Static] Serving /macaron.go\n")
resp = httptest.NewRecorder()
req, err = http.NewRequest("GET", "http://localhost:4000/inject/inject.go", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
So(resp.Code, ShouldEqual, http.StatusOK)
So(buf.String(), ShouldEndWith, "[Macaron] [Static] Serving /inject/inject.go\n")
})
})
}
+421
View File
@@ -0,0 +1,421 @@
// Copyright 2013 Beego Authors
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package macaron
// NOTE: last sync 0c93364 on Dec 19, 2014.
import (
"path"
"regexp"
"strings"
"github.com/Unknwon/com"
)
type leafInfo struct {
// Names of wildcards that lead to this leaf.
// eg, ["id" "name"] for the wildcard ":id" and ":name".
wildcards []string
// Not nil if the leaf is regexp.
regexps *regexp.Regexp
handle Handle
}
func (leaf *leafInfo) match(wildcardValues []string) (ok bool, params Params) {
if leaf.regexps == nil {
if len(wildcardValues) == 0 && len(leaf.wildcards) > 0 {
if com.IsSliceContainsStr(leaf.wildcards, ":") {
params = make(map[string]string)
j := 0
for _, v := range leaf.wildcards {
if v == ":" {
continue
}
params[v] = ""
j += 1
}
return true, params
}
return false, nil
} else if len(wildcardValues) == 0 {
return true, nil // Static path.
}
// Match *
if len(leaf.wildcards) == 1 && leaf.wildcards[0] == ":splat" {
params = make(map[string]string)
params[":splat"] = path.Join(wildcardValues...)
return true, params
}
// Match *.*
if len(leaf.wildcards) == 3 && leaf.wildcards[0] == "." {
params = make(map[string]string)
lastone := wildcardValues[len(wildcardValues)-1]
strs := strings.SplitN(lastone, ".", 2)
if len(strs) == 2 {
params[":ext"] = strs[1]
} else {
params[":ext"] = ""
}
params[":path"] = path.Join(wildcardValues[:len(wildcardValues)-1]...) + "/" + strs[0]
return true, params
}
// Match :id
params = make(map[string]string)
j := 0
for _, v := range leaf.wildcards {
if v == ":" {
continue
}
if v == "." {
lastone := wildcardValues[len(wildcardValues)-1]
strs := strings.SplitN(lastone, ".", 2)
if len(strs) == 2 {
params[":ext"] = strs[1]
} else {
params[":ext"] = ""
}
if len(wildcardValues[j:]) == 1 {
params[":path"] = strs[0]
} else {
params[":path"] = path.Join(wildcardValues[j:]...) + "/" + strs[0]
}
return true, params
}
if len(wildcardValues) <= j {
return false, nil
}
params[v] = wildcardValues[j]
j++
}
if len(params) != len(wildcardValues) {
return false, nil
}
return true, params
}
if !leaf.regexps.MatchString(path.Join(wildcardValues...)) {
return false, nil
}
params = make(map[string]string)
matches := leaf.regexps.FindStringSubmatch(path.Join(wildcardValues...))
for i, match := range matches[1:] {
params[leaf.wildcards[i]] = match
}
return true, params
}
// Tree represents a router tree for Macaron instance.
type Tree struct {
fixroutes map[string]*Tree
wildcard *Tree
leaves []*leafInfo
}
// NewTree initializes and returns a router tree.
func NewTree() *Tree {
return &Tree{
fixroutes: make(map[string]*Tree),
}
}
// splitPath splites patthen into parts.
//
// Examples:
// "/" -> []
// "/admin" -> ["admin"]
// "/admin/" -> ["admin"]
// "/admin/users" -> ["admin", "users"]
func splitPath(pattern string) []string {
if len(pattern) == 0 {
return []string{}
}
elements := strings.Split(pattern, "/")
if elements[0] == "" {
elements = elements[1:]
}
if elements[len(elements)-1] == "" {
elements = elements[:len(elements)-1]
}
return elements
}
// AddRouter adds a new route to router tree.
func (t *Tree) AddRouter(pattern string, handle Handle) {
t.addSegments(splitPath(pattern), handle, nil, "")
}
// splitSegment splits segment into parts.
//
// Examples:
// "admin" -> false, nil, ""
// ":id" -> true, [:id], ""
// "?:id" -> true, [: :id], "" : meaning can empty
// ":id:int" -> true, [:id], ([0-9]+)
// ":name:string" -> true, [:name], ([\w]+)
// ":id([0-9]+)" -> true, [:id], ([0-9]+)
// ":id([0-9]+)_:name" -> true, [:id :name], ([0-9]+)_(.+)
// "cms_:id_:page.html" -> true, [:id :page], cms_(.+)_(.+).html
// "*" -> true, [:splat], ""
// "*.*" -> true,[. :path :ext], "" . meaning separator
func splitSegment(key string) (bool, []string, string) {
if strings.HasPrefix(key, "*") {
if key == "*.*" {
return true, []string{".", ":path", ":ext"}, ""
} else {
return true, []string{":splat"}, ""
}
}
if strings.ContainsAny(key, ":") {
var paramsNum int
var out []rune
var start bool
var startexp bool
var param []rune
var expt []rune
var skipnum int
params := []string{}
reg := regexp.MustCompile(`[a-zA-Z0-9]+`)
for i, v := range key {
if skipnum > 0 {
skipnum -= 1
continue
}
if start {
//:id:int and :name:string
if v == ':' {
if len(key) >= i+4 {
if key[i+1:i+4] == "int" {
out = append(out, []rune("([0-9]+)")...)
params = append(params, ":"+string(param))
start = false
startexp = false
skipnum = 3
param = make([]rune, 0)
paramsNum += 1
continue
}
}
if len(key) >= i+7 {
if key[i+1:i+7] == "string" {
out = append(out, []rune(`([\w]+)`)...)
params = append(params, ":"+string(param))
paramsNum += 1
start = false
startexp = false
skipnum = 6
param = make([]rune, 0)
continue
}
}
}
// params only support a-zA-Z0-9
if reg.MatchString(string(v)) {
param = append(param, v)
continue
}
if v != '(' {
out = append(out, []rune(`(.+)`)...)
params = append(params, ":"+string(param))
param = make([]rune, 0)
paramsNum += 1
start = false
startexp = false
}
}
if startexp {
if v != ')' {
expt = append(expt, v)
continue
}
}
if v == ':' {
param = make([]rune, 0)
start = true
} else if v == '(' {
startexp = true
start = false
params = append(params, ":"+string(param))
paramsNum += 1
expt = make([]rune, 0)
expt = append(expt, '(')
} else if v == ')' {
startexp = false
expt = append(expt, ')')
out = append(out, expt...)
param = make([]rune, 0)
} else if v == '?' {
params = append(params, ":")
} else {
out = append(out, v)
}
}
if len(param) > 0 {
if paramsNum > 0 {
out = append(out, []rune(`(.+)`)...)
}
params = append(params, ":"+string(param))
}
return true, params, string(out)
} else {
return false, nil, ""
}
}
// addSegments add segments to the router tree.
func (t *Tree) addSegments(segments []string, handle Handle, wildcards []string, reg string) {
// Fixed root route.
if len(segments) == 0 {
if reg != "" {
filterCards := make([]string, 0, len(wildcards))
for _, v := range wildcards {
if v == ":" || v == "." {
continue
}
filterCards = append(filterCards, v)
}
t.leaves = append(t.leaves, &leafInfo{
handle: handle,
wildcards: filterCards,
regexps: regexp.MustCompile("^" + reg + "$"),
})
} else {
t.leaves = append(t.leaves, &leafInfo{
handle: handle,
wildcards: wildcards,
})
}
return
}
seg := segments[0]
iswild, params, regexpStr := splitSegment(seg)
//for the router /login/*/access match /login/2009/11/access
if !iswild && com.IsSliceContainsStr(wildcards, ":splat") {
iswild = true
regexpStr = seg
}
if seg == "*" && len(wildcards) > 0 && reg == "" {
iswild = true
regexpStr = "(.+)"
}
if iswild {
if t.wildcard == nil {
t.wildcard = NewTree()
}
if regexpStr != "" {
if reg == "" {
rr := ""
for _, w := range wildcards {
if w == "." || w == ":" {
continue
}
if w == ":splat" {
rr = rr + "(.+)/"
} else {
rr = rr + "([^/]+)/"
}
}
regexpStr = rr + regexpStr
} else {
regexpStr = "/" + regexpStr
}
} else if reg != "" {
if seg == "*.*" {
regexpStr = "/([^.]+).(.+)"
} else {
for _, w := range params {
if w == "." || w == ":" {
continue
}
regexpStr = "/([^/]+)" + regexpStr
}
}
}
t.wildcard.addSegments(segments[1:], handle, append(wildcards, params...), reg+regexpStr)
} else {
subTree, ok := t.fixroutes[seg]
if !ok {
subTree = NewTree()
t.fixroutes[seg] = subTree
}
subTree.addSegments(segments[1:], handle, wildcards, reg)
}
}
func (t *Tree) match(segments []string, wildcardValues []string) (handle Handle, params Params) {
// Handle leaf nodes.
if len(segments) == 0 {
for _, l := range t.leaves {
if ok, pa := l.match(wildcardValues); ok {
return l.handle, pa
}
}
if t.wildcard != nil {
for _, l := range t.wildcard.leaves {
if ok, pa := l.match(wildcardValues); ok {
return l.handle, pa
}
}
}
return nil, nil
}
seg, segs := segments[0], segments[1:]
subTree, ok := t.fixroutes[seg]
if ok {
handle, params = subTree.match(segs, wildcardValues)
} else if len(segs) == 0 { //.json .xml
if subindex := strings.LastIndex(seg, "."); subindex != -1 {
subTree, ok = t.fixroutes[seg[:subindex]]
if ok {
handle, params = subTree.match(segs, wildcardValues)
if handle != nil {
if params == nil {
params = make(map[string]string)
}
params[":ext"] = seg[subindex+1:]
return handle, params
}
}
}
}
if handle == nil && t.wildcard != nil {
handle, params = t.wildcard.match(segs, append(wildcardValues, seg))
}
if handle == nil {
for _, l := range t.leaves {
if ok, pa := l.match(append(wildcardValues, segments...)); ok {
return l.handle, pa
}
}
}
return handle, params
}
// Match returns Handle and params if any route is matched.
func (t *Tree) Match(pattern string) (Handle, Params) {
if len(pattern) == 0 || pattern[0] != '/' {
return nil, nil
}
return t.match(splitPath(pattern), nil)
}
+112
View File
@@ -0,0 +1,112 @@
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package macaron
import (
// "net/http"
"strings"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func Test_splitSegment(t *testing.T) {
type result struct {
Ok bool
Parts []string
Regex string
}
cases := map[string]result{
"admin": result{false, nil, ""},
":id": result{true, []string{":id"}, ""},
"?:id": result{true, []string{":", ":id"}, ""},
":id:int": result{true, []string{":id"}, "([0-9]+)"},
":name:string": result{true, []string{":name"}, `([\w]+)`},
":id([0-9]+)": result{true, []string{":id"}, "([0-9]+)"},
":id([0-9]+)_:name": result{true, []string{":id", ":name"}, "([0-9]+)_(.+)"},
"cms_:id_:page.html": result{true, []string{":id", ":page"}, "cms_(.+)_(.+).html"},
"*": result{true, []string{":splat"}, ""},
"*.*": result{true, []string{".", ":path", ":ext"}, ""},
}
Convey("Splits segment into parts", t, func() {
for key, result := range cases {
ok, parts, regex := splitSegment(key)
So(ok, ShouldEqual, result.Ok)
if result.Parts == nil {
So(parts, ShouldBeNil)
} else {
So(parts, ShouldNotBeNil)
So(strings.Join(parts, " "), ShouldEqual, strings.Join(result.Parts, " "))
}
So(regex, ShouldEqual, result.Regex)
}
})
}
func Test_Tree_Match(t *testing.T) {
type result struct {
pattern string
reqUrl string
params map[string]string
}
cases := []result{
{"/:id", "/123", map[string]string{":id": "123"}},
{"/hello/?:id", "/hello", map[string]string{":id": ""}},
{"/", "/", nil},
{"", "", nil},
{"/customer/login", "/customer/login", nil},
{"/customer/login", "/customer/login.json", map[string]string{":ext": "json"}},
{"/*", "/customer/123", map[string]string{":splat": "customer/123"}},
{"/*", "/customer/2009/12/11", map[string]string{":splat": "customer/2009/12/11"}},
{"/aa/*/bb", "/aa/2009/bb", map[string]string{":splat": "2009"}},
{"/cc/*/dd", "/cc/2009/11/dd", map[string]string{":splat": "2009/11"}},
{"/ee/:year/*/ff", "/ee/2009/11/ff", map[string]string{":year": "2009", ":splat": "11"}},
{"/thumbnail/:size/uploads/*", "/thumbnail/100x100/uploads/items/2014/04/20/dPRCdChkUd651t1Hvs18.jpg",
map[string]string{":size": "100x100", ":splat": "items/2014/04/20/dPRCdChkUd651t1Hvs18.jpg"}},
{"/*.*", "/nice/api.json", map[string]string{":path": "nice/api", ":ext": "json"}},
{"/:name/*.*", "/nice/api.json", map[string]string{":name": "nice", ":path": "api", ":ext": "json"}},
{"/:name/test/*.*", "/nice/test/api.json", map[string]string{":name": "nice", ":path": "api", ":ext": "json"}},
{"/dl/:width:int/:height:int/*.*", "/dl/48/48/05ac66d9bda00a3acf948c43e306fc9a.jpg",
map[string]string{":width": "48", ":height": "48", ":ext": "jpg", ":path": "05ac66d9bda00a3acf948c43e306fc9a"}},
{"/v1/shop/:id:int", "/v1/shop/123", map[string]string{":id": "123"}},
{"/:year:int/:month:int/:id/:endid", "/1111/111/aaa/aaa", map[string]string{":year": "1111", ":month": "111", ":id": "aaa", ":endid": "aaa"}},
{"/v1/shop/:id/:name", "/v1/shop/123/nike", map[string]string{":id": "123", ":name": "nike"}},
{"/v1/shop/:id/account", "/v1/shop/123/account", map[string]string{":id": "123"}},
{"/v1/shop/:name:string", "/v1/shop/nike", map[string]string{":name": "nike"}},
{"/v1/shop/:id([0-9]+)", "/v1/shop//123", map[string]string{":id": "123"}},
{"/v1/shop/:id([0-9]+)_:name", "/v1/shop/123_nike", map[string]string{":id": "123", ":name": "nike"}},
{"/v1/shop/:id(.+)_cms.html", "/v1/shop/123_cms.html", map[string]string{":id": "123"}},
{"/v1/shop/cms_:id(.+)_:page(.+).html", "/v1/shop/cms_123_1.html", map[string]string{":id": "123", ":page": "1"}},
{"/v1/:v/cms/aaa_:id(.+)_:page(.+).html", "/v1/2/cms/aaa_123_1.html", map[string]string{":v": "2", ":id": "123", ":page": "1"}},
{"/v1/:v/cms_:id(.+)_:page(.+).html", "/v1/2/cms_123_1.html", map[string]string{":v": "2", ":id": "123", ":page": "1"}},
{"/v1/:v(.+)_cms/ttt_:id(.+)_:page(.+).html", "/v1/2_cms/ttt_123_1.html", map[string]string{":v": "2", ":id": "123", ":page": "1"}},
}
Convey("Match routers in tree", t, func() {
for _, c := range cases {
t := NewTree()
t.AddRouter(c.pattern, nil)
_, params := t.Match(c.reqUrl)
if params != nil {
for k, v := range c.params {
vv, ok := params[k]
So(ok, ShouldBeTrue)
So(vv, ShouldEqual, v)
}
}
}
})
}
+50
View File
@@ -0,0 +1,50 @@
slug
====
Package `slug` generate slug from unicode string, URL-friendly slugify with
multiple languages support.
[![GoDoc](https://godoc.org/github.com/dalu/slug?status.png)](https://godoc.org/github.com/dalu/slug)
[Documentation online](http://godoc.org/github.com/dalu/slug)
## Example
package main
import(
"github.com/gosimple/slug"
"fmt"
)
func main () {
text := slug.Make("Hellö Wörld хелло ворлд")
fmt.Println(text) // Will print hello-world-khello-vorld
someText := slug.Make("影師")
fmt.Println(someText) // Will print: ying-shi
enText := slug.MakeLang("This & that", "en")
fmt.Println(enText) // Will print 'this-and-that'
deText := slug.MakeLang("Diese & Dass", "de")
fmt.Println(deText) // Will print 'diese-und-dass'
slug.CustomSub = map[string]string{
"water": "sand",
}
textSub := slug.Make("water is hot")
fmt.Println(textSub) // Will print 'sand-is-hot'
}
## Installation
go get -u github.com/dalu/slug
## License
The source files are distributed under the
[Mozilla Public License, version 2.0](http://mozilla.org/MPL/2.0/),
unless otherwise noted.
Please read the [FAQ](http://www.mozilla.org/MPL/2.0/FAQ.html)
if you have further questions regarding the license.
+16
View File
@@ -0,0 +1,16 @@
// Copyright 2013 by Dobrosław Żybort. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package slug
var defaultSub = map[rune]string{
'"': "",
'\'': "",
'': "",
'': "-", // figure dash
'': "-", // en dash
'—': "-", // em dash
'―': "-", // horizontal bar
}
+39
View File
@@ -0,0 +1,39 @@
// Copyright 2013 by Dobrosław Żybort. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
/*
Package slug generate slug from unicode string, URL-friendly slugify with
multiple languages support.
Example:
package main
import(
"github.com/dalu/slug"
"fmt"
)
func main () {
text := slug.Make("Hellö Wörld хелло ворлд")
fmt.Println(text) // Will print hello-world-khello-vorld
someText := slug.Make("影師")
fmt.Println(someText) // Will print: ying-shi
enText := slug.MakeLang("This & that", "en")
fmt.Println(enText) // Will print 'this-and-that'
deText := slug.MakeLang("Diese & Dass", "de")
fmt.Println(deText) // Will print 'diese-und-dass'
slug.CustomSub = map[string]string{
"water": "sand",
}
textSub := slug.Make("water is hot")
fmt.Println(textSub) // Will print 'sand-is-hot'
}
*/
package slug
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2013 by Dobrosław Żybort. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package slug
var deSub = map[rune]string{
'&': "und",
'@': "an",
}
var enSub = map[rune]string{
'&': "and",
'@': "at",
}
var plSub = map[rune]string{
'&': "i",
'@': "na",
}
var esSub = map[rune]string{
'&': "y",
'@': "en",
}
+122
View File
@@ -0,0 +1,122 @@
// Copyright 2013 by Dobrosław Żybort. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package slug
import (
"github.com/dalu/unidecode"
"regexp"
"strings"
)
var (
// Custom substitution map
CustomSub map[string]string
// Custom rune substitution map
CustomRuneSub map[rune]string
// Maximum slug length. It's smart so it will cat slug after full word.
// By default slugs aren't shortened.
// If MaxLength is smaller than length of the first word, then returned
// slug will contain only substring from the first word truncated
// after MaxLength.
MaxLength int
)
//=============================================================================
// Make returns slug generated from provided string. Will use "en" as language
// substitution.
func Make(s string) (slug string) {
return MakeLang(s, "en")
}
// MakeLang returns slug generated from provided string and will use provided
// language for chars substitution.
func MakeLang(s string, lang string) (slug string) {
slug = strings.TrimSpace(s)
// Custom substitutions
// Always substitute runes first
slug = SubstituteRune(slug, CustomRuneSub)
slug = Substitute(slug, CustomSub)
// Process string with selected substitution language
switch lang {
case "de":
slug = SubstituteRune(slug, deSub)
case "en":
slug = SubstituteRune(slug, enSub)
case "pl":
slug = SubstituteRune(slug, plSub)
case "es":
slug = SubstituteRune(slug, esSub)
default: // fallback to "en" if lang not found
slug = SubstituteRune(slug, enSub)
}
slug = SubstituteRune(slug, defaultSub)
// Process all non ASCII symbols
slug = unidecode.Unidecode(slug)
slug = strings.ToLower(slug)
// Process all remaining symbols
slug = regexp.MustCompile("[^a-z0-9-_]").ReplaceAllString(slug, "-")
slug = regexp.MustCompile("-+").ReplaceAllString(slug, "-")
slug = strings.Trim(slug, "-")
if MaxLength > 0 {
slug = smartTruncate(slug)
}
return slug
}
// Substitute returns string with superseded all substrings from
// provided substitution map.
func Substitute(s string, sub map[string]string) (buf string) {
buf = s
for key, val := range sub {
buf = strings.Replace(s, key, val, -1)
}
return
}
// SubstituteRune substitutes string chars with provided rune
// substitution map.
func SubstituteRune(s string, sub map[rune]string) (buf string) {
for _, c := range s {
if d, ok := sub[c]; ok {
buf += d
} else {
buf += string(c)
}
}
return
}
func smartTruncate(text string) string {
if len(text) < MaxLength {
return text
}
var truncated string
words := strings.SplitAfter(text, "-")
// If MaxLength is smaller than length of the first word return word
// truncated after MaxLength.
if len(words[0]) > MaxLength {
return words[0][:MaxLength]
}
for _, word := range words {
if len(truncated)+len(word)-1 <= MaxLength {
truncated = truncated + word
} else {
break
}
}
return strings.Trim(truncated, "-")
}
+337
View File
@@ -0,0 +1,337 @@
// Copyright 2013 by Dobrosław Żybort. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package slug
import (
"testing"
)
//=============================================================================
func TestSlugMake(t *testing.T) {
var testCases = []struct {
in string
want string
}{
{"DOBROSLAWZYBORT", "dobroslawzybort"},
{"Dobroslaw Zybort", "dobroslaw-zybort"},
{" Dobroslaw Zybort ?", "dobroslaw-zybort"},
{"Dobrosław Żybort", "dobroslaw-zybort"},
{"Ala ma 6 kotów.", "ala-ma-6-kotow"},
{"áÁàÀãÃâÂäÄąĄą̊Ą̊", "aaaaaaaaaaaaaa"},
{"ćĆĉĈçÇ", "cccccc"},
{"éÉèÈẽẼêÊëËęĘ", "eeeeeeeeeeee"},
{"íÍìÌĩĨîÎïÏįĮ", "iiiiiiiiiiii"},
{"łŁ", "ll"},
{"ńŃ", "nn"},
{"óÓòÒõÕôÔöÖǫǪǭǬø", "ooooooooooooooo"},
{"śŚ", "ss"},
{"úÚùÙũŨûÛüÜųŲ", "uuuuuuuuuuuu"},
{"y̨Y̨", "yy"},
{"źŹżŹ", "zzzz"},
{"·/,:;`˜'\"", ""},
{"20002013", "2000-2013"},
{"style—not", "style-not"},
{"test_slug", "test_slug"},
{"Æ", "ae"},
{"Ich heiße", "ich-heisse"},
{"This & that", "this-and-that"},
{"fácil €", "facil-eu"},
{"smile ☺", "smile"},
{"Hellö Wörld хелло ворлд", "hello-world-khello-vorld"},
{"\"C'est déjà l’été.\"", "cest-deja-lete"},
{"jaja---lol-méméméoo--a", "jaja-lol-mememeoo-a"},
{"影師", "ying-shi"},
}
for index, st := range testCases {
got := Make(st.in)
if got != st.want {
t.Errorf(
"%d. Make(%#v) = %#v; want %#v",
index, st.in, got, st.want)
}
}
}
func TestSlugMakeLang(t *testing.T) {
var testCases = []struct {
lang string
in string
want string
}{
{"en", "This & that", "this-and-that"},
{"de", "This & that", "this-und-that"},
{"pl", "This & that", "this-i-that"},
{"es", "This & that", "this-y-that"},
{"test", "This & that", "this-and-that"}, // unknown lang, fallback to "en"
}
for index, smlt := range testCases {
got := MakeLang(smlt.in, smlt.lang)
if got != smlt.want {
t.Errorf(
"%d. MakeLang(%#v, %#v) = %#v; want %#v",
index, smlt.in, smlt.lang, got, smlt.want)
}
}
}
func TestSlugMakeUserSubstituteLang(t *testing.T) {
var testCases = []struct {
cSub map[string]string
lang string
in string
want string
}{
{map[string]string{"'": " "}, "en", "That's great", "that-s-great"},
{map[string]string{"&": "or"}, "en", "This & that", "this-or-that"}, // by default "&" => "and"
{map[string]string{"&": "or"}, "de", "This & that", "this-or-that"}, // by default "&" => "und"
}
for index, smust := range testCases {
CustomSub = smust.cSub
got := MakeLang(smust.in, smust.lang)
if got != smust.want {
t.Errorf(
"%d. %#v; MakeLang(%#v, %#v) = %#v; want %#v",
index, smust.cSub, smust.in, smust.lang,
got, smust.want)
}
}
}
func TestSlugMakeSubstituteOrderLang(t *testing.T) {
// Always substitute runes first
var testCases = []struct {
rSub map[rune]string
sSub map[string]string
in string
want string
}{
{map[rune]string{'o': "left"}, map[string]string{"o": "right"}, "o o", "left-left"},
{map[rune]string{'&': "down"}, map[string]string{"&": "up"}, "&", "down"},
}
for index, smsot := range testCases {
CustomRuneSub = smsot.rSub
CustomSub = smsot.sSub
got := Make(smsot.in)
if got != smsot.want {
t.Errorf(
"%d. %#v; %#v; Make(%#v) = %#v; want %#v",
index, smsot.rSub, smsot.sSub, smsot.in,
got, smsot.want)
}
}
}
func TestSubstituteLang(t *testing.T) {
var testCases = []struct {
cSub map[string]string
in string
want string
}{
{map[string]string{"o": "no"}, "o o o", "no no no"},
{map[string]string{"'": " "}, "That's great", "That s great"},
}
for index, sst := range testCases {
got := Substitute(sst.in, sst.cSub)
if got != sst.want {
t.Errorf(
"%d. Substitute(%#v, %#v) = %#v; want %#v",
index, sst.in, sst.cSub, got, sst.want)
}
}
}
func TestSubstituteRuneLang(t *testing.T) {
var testCases = []struct {
cSub map[rune]string
in string
want string
}{
{map[rune]string{'o': "no"}, "o o o", "no no no"},
{map[rune]string{'\'': " "}, "That's great", "That s great"},
}
for index, ssrt := range testCases {
got := SubstituteRune(ssrt.in, ssrt.cSub)
if got != ssrt.want {
t.Errorf(
"%d. SubstituteRune(%#v, %#v) = %#v; want %#v",
index, ssrt.in, ssrt.cSub, got, ssrt.want)
}
}
}
func TestSlugMakeSmartTruncate(t *testing.T) {
var testCases = []struct {
in string
maxLength int
want string
}{
{"DOBROSLAWZYBORT", 100, "dobroslawzybort"},
{"Dobroslaw Zybort", 100, "dobroslaw-zybort"},
{"Dobroslaw Zybort", 12, "dobroslaw"},
{" Dobroslaw Zybort ?", 12, "dobroslaw"},
{"Ala ma 6 kotów.", 10, "ala-ma-6"},
{"Dobrosław Żybort", 5, "dobro"},
}
for index, smstt := range testCases {
MaxLength = smstt.maxLength
got := Make(smstt.in)
if got != smstt.want {
t.Errorf(
"%d. MaxLength = %v; Make(%#v) = %#v; want %#v",
index, smstt.maxLength, smstt.in, got, smstt.want)
}
}
}
func BenchmarkMakeShortAscii(b *testing.B) {
b.ReportAllocs()
for n := 0; n < b.N; n++ {
Make("Hello world")
}
}
func BenchmarkMakeShort(b *testing.B) {
b.ReportAllocs()
for n := 0; n < b.N; n++ {
Make("хелло ворлд")
}
}
func BenchmarkMakeShortSymbols(b *testing.B) {
b.ReportAllocs()
for n := 0; n < b.N; n++ {
Make("·/,:;`˜'\" &€£¥")
}
}
func BenchmarkMakeMediumAscii(b *testing.B) {
b.ReportAllocs()
for n := 0; n < b.N; n++ {
Make("ABCDE FGHIJ KLMNO PQRST UWXYZ ABCDE FGHIJ KLMNO PQRST UWXYZ ABCDE")
}
}
func BenchmarkMakeMedium(b *testing.B) {
b.ReportAllocs()
for n := 0; n < b.N; n++ {
Make("ヲァィゥェ ォャュョッ ーアイウエ オカキクケ コサシスセ ソタチツテ トナニヌネ ノハヒフヘ ホマミムメ モヤユヨラ リルレロワ")
}
}
func BenchmarkMakeLongAscii(b *testing.B) {
longStr := "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi " +
"pulvinar sodales ultrices. Nulla facilisi. Sed at vestibulum erat. Ut " +
"sit amet urna posuere, sagittis eros ac, varius nisi. Morbi ullamcorper " +
"odio at nunc pulvinar mattis. Vestibulum rutrum, ante eu dictum mattis, " +
"elit risus finibus nunc, consectetur facilisis eros leo ut sapien. Sed " +
"pulvinar volutpat mi. Cras semper mi ac eros accumsan, at feugiat massa " +
"elementum. Morbi eget dolor sit amet purus condimentum egestas non ut " +
"sapien. Duis feugiat magna vitae nisi lobortis, quis finibus sem " +
"sollicitudin. Pellentesque eleifend blandit ipsum, ut porta arcu " +
"ultricies et. Fusce vel ipsum porta, placerat diam ac, consectetur " +
"magna. Nulla in porta sem. Suspendisse commodo, felis in molestie " +
"ultricies, arcu ipsum aliquet turpis, elementum dapibus ipsum lorem a " +
"nisl. Etiam varius imperdiet placerat. Aliquam euismod lacus arcu, " +
"ultrices hendrerit est pellentesque vel. Aliquam sit amet laoreet leo. " +
"Integer eros libero, mollis sed posuere."
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
Make(longStr)
}
}
func BenchmarkSubstituteRuneShort(b *testing.B) {
shortStr := "Hello/Hi world"
subs := map[rune]string{'o': "no", '/': "slash"}
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
SubstituteRune(shortStr, subs)
}
}
func BenchmarkSubstituteRuneLong(b *testing.B) {
longStr := "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi " +
"pulvinar sodales ultrices. Nulla facilisi. Sed at vestibulum erat. Ut " +
"sit amet urna posuere, sagittis eros ac, varius nisi. Morbi ullamcorper " +
"odio at nunc pulvinar mattis. Vestibulum rutrum, ante eu dictum mattis, " +
"elit risus finibus nunc, consectetur facilisis eros leo ut sapien. Sed " +
"pulvinar volutpat mi. Cras semper mi ac eros accumsan, at feugiat massa " +
"elementum. Morbi eget dolor sit amet purus condimentum egestas non ut " +
"sapien. Duis feugiat magna vitae nisi lobortis, quis finibus sem " +
"sollicitudin. Pellentesque eleifend blandit ipsum, ut porta arcu " +
"ultricies et. Fusce vel ipsum porta, placerat diam ac, consectetur " +
"magna. Nulla in porta sem. Suspendisse commodo, felis in molestie " +
"ultricies, arcu ipsum aliquet turpis, elementum dapibus ipsum lorem a " +
"nisl. Etiam varius imperdiet placerat. Aliquam euismod lacus arcu, " +
"ultrices hendrerit est pellentesque vel. Aliquam sit amet laoreet leo. " +
"Integer eros libero, mollis sed posuere."
subs := map[rune]string{
'o': "no",
'/': "slash",
'i': "done",
'E': "es",
'a': "ASD",
'1': "one",
'l': "onetwo",
}
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
SubstituteRune(longStr, subs)
}
}
func BenchmarkSmartTruncateShort(b *testing.B) {
shortStr := "Hello-world"
MaxLength = 8
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
smartTruncate(shortStr)
}
}
func BenchmarkSmartTruncateLong(b *testing.B) {
longStr := "Lorem-ipsum-dolor-sit-amet,-consectetur-adipiscing-elit.-Morbi-" +
"pulvinar-sodales-ultrices.-Nulla-facilisi.-Sed-at-vestibulum-erat.-Ut-" +
"sit-amet-urna-posuere,-sagittis-eros-ac,-varius-nisi.-Morbi-ullamcorper-" +
"odio-at-nunc-pulvinar-mattis.-Vestibulum-rutrum,-ante-eu-dictum-mattis,-" +
"elit-risus-finibus-nunc,-consectetur-facilisis-eros-leo-ut-sapien.-Sed-" +
"pulvinar-volutpat-mi.-Cras-semper-mi-ac-eros-accumsan,-at-feugiat-massa-" +
"elementum.-Morbi-eget-dolor-sit-amet-purus-condimentum-egestas-non-ut-" +
"sapien.-Duis-feugiat-magna-vitae-nisi-lobortis,-quis-finibus-sem-" +
"sollicitudin.-Pellentesque-eleifend-blandit-ipsum,-ut-porta-arcu-" +
"ultricies-et.-Fusce-vel-ipsum-porta,-placerat-diam-ac,-consectetur-" +
"magna.-Nulla-in-porta-sem.-Suspendisse-commodo,-felis-in-molestie-" +
"ultricies,-arcu-ipsum-aliquet-turpis,-elementum-dapibus-ipsum-lorem-a-" +
"nisl.-Etiam-varius-imperdiet-placerat.-Aliquam-euismod-lacus-arcu,-" +
"ultrices-hendrerit-est-pellentesque-vel.-Aliquam-sit-amet-laoreet-leo.-" +
"Integer-eros-libero,-mollis-sed-posuere."
MaxLength = 256
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
smartTruncate(longStr)
}
}
@@ -0,0 +1,23 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+6
View File
@@ -0,0 +1,6 @@
unidecode
=========
Unicode transliterator in Golang - Replaces non-ASCII characters with their ASCII approximations.
View other available versions, documentation and examples at http://gopkgs.com/unidecode
+44
View File
@@ -0,0 +1,44 @@
package unidecode
import (
"compress/zlib"
"encoding/binary"
"io"
"strings"
"sync"
)
var (
decoded = false
mutex sync.Mutex
transliterations [65536][]rune
transCount = rune(len(transliterations))
getUint16 = binary.LittleEndian.Uint16
)
func decodeTransliterations() {
r, err := zlib.NewReader(strings.NewReader(tableData))
if err != nil {
panic(err)
}
defer r.Close()
tmp1 := make([]byte, 2)
tmp2 := tmp1[:1]
for {
if _, err := io.ReadAtLeast(r, tmp1, 2); err != nil {
if err == io.EOF {
break
}
panic(err)
}
chr := getUint16(tmp1)
if _, err := io.ReadAtLeast(r, tmp2, 1); err != nil {
panic(err)
}
b := make([]byte, int(tmp2[0]))
if _, err := io.ReadFull(r, b); err != nil {
panic(err)
}
transliterations[int(chr)] = []rune(string(b))
}
}
+71
View File
@@ -0,0 +1,71 @@
// +build none
package main
import (
"bytes"
"compress/zlib"
"encoding/binary"
"fmt"
"go/format"
"io/ioutil"
"strconv"
"strings"
)
func main() {
data, err := ioutil.ReadFile("table.txt")
if err != nil {
panic(err)
}
var buf bytes.Buffer
for _, line := range strings.Split(string(data), "\n") {
if strings.HasPrefix(line, "/*") || line == "" {
continue
}
sep := strings.IndexByte(line, ':')
if sep == -1 {
panic(line)
}
val, err := strconv.ParseInt(line[:sep], 0, 32)
if err != nil {
panic(err)
}
s, err := strconv.Unquote(line[sep+2:])
if err != nil {
panic(err)
}
if s == "" {
continue
}
if err := binary.Write(&buf, binary.LittleEndian, uint16(val)); err != nil {
panic(err)
}
if err := binary.Write(&buf, binary.LittleEndian, uint8(len(s))); err != nil {
panic(err)
}
buf.WriteString(s)
}
var cbuf bytes.Buffer
w, err := zlib.NewWriterLevel(&cbuf, zlib.BestCompression)
if err != nil {
panic(err)
}
if _, err := w.Write(buf.Bytes()); err != nil {
panic(err)
}
if err := w.Close(); err != nil {
panic(err)
}
buf.Reset()
buf.WriteString("package unidecode\n")
buf.WriteString("// AUTOGENERATED - DO NOT EDIT!\n\n")
fmt.Fprintf(&buf, "var tableData = %q;\n", cbuf.String())
dst, err := format.Source(buf.Bytes())
if err != nil {
panic(err)
}
if err := ioutil.WriteFile("table.go", dst, 0644); err != nil {
panic(err)
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+63
View File
@@ -0,0 +1,63 @@
// Package unidecode implements a unicode transliterator
// which replaces non-ASCII characters with their ASCII
// approximations.
package unidecode
import (
"unicode"
"gopkgs.com/pool.v1"
)
const pooledCapacity = 64
var (
slicePool = pool.New(0)
)
// Unidecode implements a unicode transliterator, which
// replaces non-ASCII characters with their ASCII
// counterparts.
// Given an unicode encoded string, returns
// another string with non-ASCII characters replaced
// with their closest ASCII counterparts.
// e.g. Unicode("áéíóú") => "aeiou"
func Unidecode(s string) string {
if !decoded {
mutex.Lock()
if !decoded {
decodeTransliterations()
decoded = true
}
mutex.Unlock()
}
l := len(s)
var r []rune
if l > pooledCapacity {
r = make([]rune, 0, len(s))
} else {
if x := slicePool.Get(); x != nil {
r = x.([]rune)[:0]
} else {
r = make([]rune, 0, pooledCapacity)
}
}
for _, c := range s {
if c <= unicode.MaxASCII {
r = append(r, c)
continue
}
if c > unicode.MaxRune || c > transCount {
/* Ignore reserved chars */
continue
}
if d := transliterations[c]; d != nil {
r = append(r, d...)
}
}
res := string(r)
if l <= pooledCapacity {
slicePool.Put(r)
}
return res
}

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