Merge pull request #1 from iobridge/master

Merge from iobridge
This commit is contained in:
franck 2014-04-03 11:26:40 +02:00
commit c4fb98ef7f
229 changed files with 4829 additions and 854 deletions

View File

@ -1,10 +1,10 @@
source 'http://rubygems.org'
gem 'rails', '4.0.2'
gem 'rails', '4.0.3'
gem 'jquery-rails', '3.0.4'
gem 'rails_autolink'
gem 'mysql2'
gem 'authlogic'
gem 'devise'
gem 'twitter_oauth', git: 'git://github.com/moomerman/twitter_oauth.git'
gem 'therubyracer'
gem 'exception_notification'
@ -29,8 +29,9 @@ gem 'capistrano', '~> 2.15.4'
gem 'rack-utf8_sanitizer'
gem 'newrelic_rpm'
gem 'actionpack-xml_parser'
gem 'activeadmin', github: 'gregbell/active_admin'
# To use debugger
# to use debugger
# gem 'ruby-debug'
# assets
@ -41,7 +42,7 @@ gem 'uglifier'
group :development do
gem 'annotate', '~> 2.6.1'
gem 'quiet_assets'
gem 'thin'
gem 'puma'
gem 'i18n-tasks', '~> 0.2.10'
end

View File

@ -1,3 +1,21 @@
GIT
remote: git://github.com/gregbell/active_admin.git
revision: 3fb7f03335b1ec5743c305f2c37103e2bfface97
specs:
activeadmin (1.0.0.pre)
arbre (~> 1.0)
bourbon
coffee-rails
devise (~> 3.2)
formtastic (~> 2.3.0.rc2)
inherited_resources (~> 1.3)
jquery-rails
jquery-ui-rails
kaminari (~> 0.15)
rails (>= 3.2, < 4.1)
ransack (~> 1.0)
sass-rails
GIT
remote: git://github.com/moomerman/twitter_oauth.git
revision: 04e6bbfe635a376cae342d234214cdab864fe797
@ -17,27 +35,27 @@ GEM
remote: http://rubygems.org/
specs:
ZenTest (4.9.5)
actionmailer (4.0.2)
actionpack (= 4.0.2)
actionmailer (4.0.3)
actionpack (= 4.0.3)
mail (~> 2.5.4)
actionpack (4.0.2)
activesupport (= 4.0.2)
actionpack (4.0.3)
activesupport (= 4.0.3)
builder (~> 3.1.0)
erubis (~> 2.7.0)
rack (~> 1.5.2)
rack-test (~> 0.6.2)
actionpack-xml_parser (1.0.1)
actionpack (>= 4.0.0.rc1)
activemodel (4.0.2)
activesupport (= 4.0.2)
activemodel (4.0.3)
activesupport (= 4.0.3)
builder (~> 3.1.0)
activerecord (4.0.2)
activemodel (= 4.0.2)
activerecord (4.0.3)
activemodel (= 4.0.3)
activerecord-deprecated_finders (~> 1.0.2)
activesupport (= 4.0.2)
activesupport (= 4.0.3)
arel (~> 4.0.0)
activerecord-deprecated_finders (1.0.3)
activesupport (4.0.2)
activesupport (4.0.3)
i18n (~> 0.6, >= 0.6.4)
minitest (~> 4.2)
multi_json (~> 1.3)
@ -51,15 +69,20 @@ GEM
annotate (2.6.1)
activerecord (>= 2.3.0)
rake (>= 0.8.7)
arel (4.0.1)
arbre (1.0.1)
activesupport (>= 3.0.0)
arel (4.0.2)
atomic (1.1.14)
authlogic (3.3.0)
activerecord (>= 3.2)
activesupport (>= 3.2)
autotest (4.4.6)
ZenTest (>= 4.4.1)
autotest-rails (4.2.1)
ZenTest (~> 4.5)
bcrypt (3.1.7)
bcrypt-ruby (3.1.5)
bcrypt (>= 3.1.3)
bourbon (3.1.8)
sass (>= 3.2.0)
thor
builder (3.1.4)
capistrano (2.15.4)
highline
@ -78,6 +101,12 @@ GEM
daemons (1.1.9)
dalli (2.7.0)
database_cleaner (1.2.0)
devise (3.2.3)
bcrypt-ruby (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 3.2.6, < 5)
thread_safe (~> 0.1)
warden (~> 1.2.3)
diff-lcs (1.2.5)
dynamic_form (1.1.4)
easy_translate (0.4.0)
@ -111,9 +140,14 @@ GEM
i18n (~> 0.5)
faraday (0.8.9)
multipart-post (~> 1.2.0)
formtastic (2.3.0.rc2)
actionpack (>= 3.0)
geokit (1.8.4)
multi_json (>= 1.3.2)
gravatarify (3.1.0)
has_scope (0.6.0.rc)
actionpack (>= 3.2, < 5)
activesupport (>= 3.2, < 5)
highline (1.6.20)
hike (1.2.3)
http_parser.rb (0.6.0)
@ -126,13 +160,21 @@ GEM
rake
term-ansicolor
terminal-table
inherited_resources (1.4.1)
has_scope (~> 0.6.0.rc)
responders (~> 1.0.0.rc)
jquery-rails (3.0.4)
railties (>= 3.0, < 5.0)
thor (>= 0.14, < 2.0)
jquery-ui-rails (4.2.0)
railties (>= 3.2.16)
json (1.8.1)
json_spec (1.1.1)
multi_json (~> 1.0)
rspec (~> 2.0)
kaminari (0.15.1)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
kgio (2.8.1)
libv8 (3.16.14.3)
mail (2.5.4)
@ -157,7 +199,12 @@ GEM
nokogiri (1.6.1)
mini_portile (~> 0.5.0)
oauth (0.4.7)
polyglot (0.3.3)
orm_adapter (0.5.0)
polyamorous (0.6.4)
activerecord (>= 3.0)
polyglot (0.3.4)
puma (2.8.1)
rack (>= 1.1, < 2.0)
quiet_assets (1.0.2)
railties (>= 3.1, < 5.0)
rack (1.5.2)
@ -167,26 +214,32 @@ GEM
rack (>= 1.0)
rack-utf8_sanitizer (1.1.0)
rack (~> 1.0)
rails (4.0.2)
actionmailer (= 4.0.2)
actionpack (= 4.0.2)
activerecord (= 4.0.2)
activesupport (= 4.0.2)
rails (4.0.3)
actionmailer (= 4.0.3)
actionpack (= 4.0.3)
activerecord (= 4.0.3)
activesupport (= 4.0.3)
bundler (>= 1.3.0, < 2.0)
railties (= 4.0.2)
railties (= 4.0.3)
sprockets-rails (~> 2.0.0)
rails_autolink (1.1.5)
rails (> 3.1)
railties (4.0.2)
actionpack (= 4.0.2)
activesupport (= 4.0.2)
railties (4.0.3)
actionpack (= 4.0.3)
activesupport (= 4.0.3)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rake (10.1.1)
ransack (1.1.0)
actionpack (>= 3.0)
activerecord (>= 3.0)
polyamorous (~> 0.6.0)
redis (3.0.7)
redis-namespace (1.4.1)
redis (~> 3.0.4)
ref (1.0.5)
responders (1.0.0)
railties (>= 3.2, < 5)
resque (1.25.1)
mono_logger (~> 1.0)
multi_json (~> 1.0)
@ -228,7 +281,7 @@ GEM
rack-protection (~> 1.4)
tilt (~> 1.3, >= 1.3.4)
spork (0.9.2)
sprockets (2.10.1)
sprockets (2.11.0)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
@ -246,10 +299,6 @@ GEM
therubyracer (0.12.0)
libv8 (~> 3.16.14.0)
ref
thin (1.6.1)
daemons (>= 1.0.9)
eventmachine (>= 1.0.0)
rack (>= 1.0.0)
thor (0.18.1)
thread (0.1.3)
thread_safe (0.1.3)
@ -275,6 +324,8 @@ GEM
json (>= 1.8.0)
vegas (0.1.11)
rack (>= 1.0.0)
warden (1.2.3)
rack (>= 1.0)
webrat (0.7.3)
nokogiri (>= 1.2.0)
rack (>= 1.0)
@ -287,10 +338,10 @@ PLATFORMS
DEPENDENCIES
ZenTest
actionpack-xml_parser
activeadmin!
acts_as_list
acts_as_tree
annotate (~> 2.6.1)
authlogic
autotest
autotest-rails
capistrano (~> 2.15.4)
@ -298,6 +349,7 @@ DEPENDENCIES
daemons
dalli
database_cleaner (~> 1.2.0)
devise
dynamic_form
exception_notification
factory_girl_rails
@ -312,9 +364,10 @@ DEPENDENCIES
nested_form
newrelic_rpm
nokogiri
puma
quiet_assets
rack-utf8_sanitizer
rails (= 4.0.2)
rails (= 4.0.3)
rails_autolink
redis
resque-scheduler (= 2.3.1)
@ -325,7 +378,6 @@ DEPENDENCIES
spork
sqlite3-ruby (= 1.3.3)
therubyracer
thin
tweetstream
twitter_oauth!
uglifier

25
app/admin/channel.rb Normal file
View File

@ -0,0 +1,25 @@
ActiveAdmin.register Channel do
filter :name
filter :description
filter :created_at
permit_params :name, :public_flag
index do
column :id
column(:name) { |channel| link_to channel.name, channel }
column(:user) { |channel| link_to channel.user.login, admin_user_path(channel.user) if channel.user.present? }
column :public_flag
column :created_at
default_actions
end
form do |f|
f.semantic_errors *f.object.errors.keys
f.inputs :name, :public_flag
f.actions
end
end

28
app/admin/dashboard.rb Normal file
View File

@ -0,0 +1,28 @@
ActiveAdmin.register_page "Dashboard" do
menu priority: 1, label: proc{ I18n.t("active_admin.dashboard") }
content title: proc{ I18n.t("active_admin.dashboard") } do
columns do
column do
panel "Stats" do
para "Total Users: #{User.all.count}"
para "Total Channels: #{Channel.all.count}"
end
end
column do
panel "Recent Channels" do
ul do
Channel.all.order("created_at desc").limit(5).map do |channel|
li link_to(channel.name, admin_channel_path(channel))
end
end
end
end
end
end # content
end

19
app/admin/failedlogin.rb Normal file
View File

@ -0,0 +1,19 @@
ActiveAdmin.register Failedlogin do
menu :parent => "Others"
actions :all, :except => [:edit]
filter :login
filter :password
filter :created_at
index do
column :id
column :login
column :password
column :ip_address
column :created_at
default_actions
end
end

23
app/admin/plugin.rb Normal file
View File

@ -0,0 +1,23 @@
ActiveAdmin.register Plugin do
filter :name
filter :created_at
permit_params :name, :html, :css, :js, :private_flag
index do
column :id
column(:user) { |object| link_to object.user.login, admin_user_path(object.user) if object.user.present? }
column :name
column :private_flag
default_actions
end
form do |f|
f.semantic_errors *f.object.errors.keys
f.inputs :name, :html, :css, :js, :private_flag
f.actions
end
end

View File

@ -0,0 +1,9 @@
ActiveAdmin.register_page "Useful Links" do
menu :parent => "Others"
content do
render "index"
end
end

48
app/admin/user.rb Normal file
View File

@ -0,0 +1,48 @@
ActiveAdmin.register User do
require 'csv'
filter :email
filter :login
filter :created_at
permit_params :email, :login, :bio, :website
index do
column :id
column :email
column :login
column :created_at
default_actions
end
show do
attributes_table do
rows :id, :email, :login, :time_zone, :bio, :website, :created_at, :sign_in_count, :current_sign_in_at, :last_sign_in_at, :current_sign_in_ip, :last_sign_in_ip
end
panel 'Channels' do
table_for user.channels do
column :id
column(:name) { |channel| link_to channel.name, channel }
end
end
end
form do |f|
f.semantic_errors *f.object.errors.keys
f.inputs :email, :login
f.actions
end
# custom action for signups per day
collection_action :signups, :method => :get, :format => :csv do
@csv_headers = [:day, :signups]
@days = User.signups_per_day
end
# custom action for emails list
collection_action :emails, :method => :get do
@users = User.all
end
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

View File

@ -0,0 +1,2 @@
#= require active_admin/base

View File

@ -1,6 +1,19 @@
// when the document is ready
$(document).ready(function() {
// allow flash notices to be dismissed
if ($(".flash").length > 0) {
$(".flash").on("click", function() {
$(this).hide("slow");
});
// hide flash automatically after 15 seconds
setTimeout(function() {
if ($(".flash").length > 0) {
$(".flash").hide("slow");
}
}, 15000);
}
// show form to add a talkback command
$('#talkback_command_add').click(function() {
$(this).hide();

View File

@ -208,7 +208,7 @@ o(b[3],a[3]);this.axisOffset=[0,0,0,0];this.clipOffset=[0,0,0,0]},drawChartBox:f
null,null,c-p,d-p));else{e={fill:j||Q};if(i)e.stroke=a.borderColor,e["stroke-width"]=i;this.chartBackground=b.rect(p/2,p/2,c-p,d-p,a.borderRadius,i).attr(e).add().shadow(a.shadow)}if(k)f?f.animate(s):this.plotBackground=b.rect(q,o,n,t,0).attr({fill:k}).add().shadow(a.plotShadow);if(l)h?h.animate(s):this.plotBGImage=b.image(l,q,o,n,t).add();r?r.animate({width:w.width,height:w.height}):this.clipRect=b.clipRect(w);if(m)g?g.animate(g.crisp(null,q,o,n,t)):this.plotBorder=b.rect(q,o,n,t,0,-m).attr({stroke:a.plotBorderColor,
"stroke-width":m,zIndex:1}).add();this.isDirtyBox=!1},propFromSeries:function(){var a=this,b=a.options.chart,c,d=a.options.series,e,f;n(["inverted","angular","polar"],function(g){c=L[b.type||b.defaultSeriesType];f=a[g]||b[g]||c&&c.prototype[g];for(e=d&&d.length;!f&&e--;)(c=L[d[e].type])&&c.prototype[g]&&(f=!0);a[g]=f})},linkSeries:function(){var a=this,b=a.series;n(b,function(a){a.linkedSeries.length=0});n(b,function(b){var d=b.options.linkedTo;if(da(d)&&(d=d===":previous"?a.series[b.index-1]:a.get(d)))d.linkedSeries.push(b),
b.linkedParent=d})},render:function(){var a=this,b=a.axes,c=a.renderer,d=a.options,e=d.labels,f=d.credits,g;a.setTitle();a.legend=new zb(a,d.legend);a.getStacks();n(b,function(a){a.setScale()});a.getMargins();a.maxTicks=null;n(b,function(a){a.setTickPositions(!0);a.setMaxTicks()});a.adjustTickAmounts();a.getMargins();a.drawChartBox();a.hasCartesianSeries&&n(b,function(a){a.render()});if(!a.seriesGroup)a.seriesGroup=c.g("series-group").attr({zIndex:3}).add();n(a.series,function(a){a.translate();a.setTooltipPoints();
a.render()});e.items&&n(e.items,function(b){var d=r(e.style,b.style),f=z(d.left)+a.plotLeft,g=z(d.top)+a.plotTop+12;delete d.left;delete d.top;c.text(b.html,f,g).attr({zIndex:2}).css(d).add()});if(f.enabled&&!a.credits)g=f.href,a.credits=c.text(f.text,0,0).on("click",function(){if(g)location.href=g}).attr({align:f.position.align,zIndex:8}).css(f.style).add().align(f.position);a.hasRendered=!0},destroy:function(){var a=this,b=a.axes,c=a.series,d=a.container,e,f=d&&d.parentNode;A(a,"destroy");Ja[a.index]=
a.render()});e.items&&n(e.items,function(b){var d=r(e.style,b.style),f=z(d.left)+a.plotLeft,g=z(d.top)+a.plotTop+12;delete d.left;delete d.top;c.text(b.html,f,g).attr({zIndex:2}).css(d).add()});if(f.enabled&&!a.credits)g=f.href,a.credits=c.text(f.text,0,0).on("click",function(){if(g)parent.location.href=g}).attr({align:f.position.align,zIndex:8}).css(f.style).add().align(f.position);a.hasRendered=!0},destroy:function(){var a=this,b=a.axes,c=a.series,d=a.container,e,f=d&&d.parentNode;A(a,"destroy");Ja[a.index]=
u;a.renderTo.removeAttribute("data-highcharts-chart");X(a);for(e=b.length;e--;)b[e]=b[e].destroy();for(e=c.length;e--;)c[e]=c[e].destroy();n("title,subtitle,chartBackground,plotBackground,plotBGImage,plotBorder,seriesGroup,clipRect,credits,pointer,scroller,rangeSelector,legend,resetZoomButton,tooltip,renderer".split(","),function(b){var c=a[b];c&&c.destroy&&(a[b]=c.destroy())});if(d)d.innerHTML="",X(d),f&&Oa(d);for(e in a)delete a[e]},isReadyToRender:function(){var a=this;return!V&&C==C.top&&v.readyState!==
"complete"||ba&&!C.canvg?(ba?Mb.push(function(){a.firstRender()},a.options.global.canvasToolsURL):v.attachEvent("onreadystatechange",function(){v.detachEvent("onreadystatechange",a.firstRender);v.readyState==="complete"&&a.firstRender()}),!1):!0},firstRender:function(){var a=this,b=a.options,c=a.callback;if(a.isReadyToRender())a.getContainer(),A(a,"init"),a.resetMargins(),a.setChartSize(),a.propFromSeries(),a.getAxes(),n(b.series||[],function(b){a.initSeries(b)}),a.linkSeries(),A(a,"beforeRender"),
a.pointer=new Za(a,b),a.render(),a.renderer.draw(),c&&c.apply(a,[a]),n(a.callbacks,function(b){b.apply(a,[a])}),a.cloneRenderTo(!0),A(a,"load")},splashArray:function(a,b){var c=b[a],c=S(c)?c:[c,c,c,c];return[o(b[a+"Top"],c[0]),o(b[a+"Right"],c[1]),o(b[a+"Bottom"],c[2]),o(b[a+"Left"],c[3])]}};eb.prototype.callbacks=[];var xb=Highcharts.CenteredSeriesMixin={getCenter:function(){var a=this.options,b=this.chart,c=2*(a.slicedOffset||0),d,e=b.plotWidth-2*c,f=b.plotHeight-2*c,b=a.center,a=[o(b[0],"50%"),

View File

@ -1,5 +1,6 @@
// if on api subdomain, redirect
// if on api subdomain except for charts, redirect
var wloc = window.location.toString();
if (wloc.indexOf('api') != -1 && wloc.indexOf('api') < 10) {
if (wloc.indexOf('api') !== -1 && wloc.indexOf('api') < 10 && wloc.indexOf('charts') === -1) {
window.location = wloc.replace('api', 'www');
}
}

View File

@ -1,9 +1,12 @@
$(document).ready(function() {
// execute on window load (and not document.ready), so that the sidebar is positioned correctly
$(window).load(function() {
// if affix function exists
if ($.fn.affix) {
// add sidebar affix
$('#bootstrap-sidebar').affix();
// add sidebar affix, wrapped in a timeout so that it displays correctly
setTimeout(function() {
$('#bootstrap-sidebar').affix();
}, 100);
// add sidebar scrollspy
$(document.body).scrollspy({ target: '#leftcol', offset: 300 });

View File

@ -13,11 +13,11 @@ function getDimensions(element) {
}
function updateChart(index,
postUpdate,
width,
height,
channelId,
newOptionsSave) {
postUpdate,
width,
height,
channelId,
newOptionsSave) {
// default width and height
var width = width;
var height = height;
@ -26,52 +26,52 @@ function updateChart(index,
var iframe = $('#iframe' + index).attr("default_src");
if (!iframe) {
iframe = $('#iframe' + index).attr('src');
iframe = $('#iframe' + index).attr('src');
}
src = iframe.split('?')[0];
// if not a line chart, a timeslice should be present or set timescale=30
if ($('#type_' + index).val() != 'line') {
if ($('#timescale_' + index).val().length == 0 && $('#average_' + index).val().length == 0 && $('#median_' + index).val().length == 0 && $('#sum_' + index).val().length == 0) {
$('#timescale_' + index).val(30);
}
if ($('#timescale_' + index).val().length == 0 && $('#average_' + index).val().length == 0 && $('#median_' + index).val().length == 0 && $('#sum_' + index).val().length == 0) {
$('#timescale_' + index).val(30);
}
}
// add inputs to array
var inputs = [];
$('.chart_options' + index).each(function() {
var v = $(this).val();
var id = $(this).attr('id');
var tag = id.split("_")[0];
var v = $(this).val();
var id = $(this).attr('id');
var tag = id.split("_")[0];
if (v.length > 0) { inputs.push([tag, v]); }
});
if (v.length > 0) { inputs.push([tag, v]); }
});
// create querystring
var qs = '';
while (inputs.length > 0) {
var p = inputs.pop();
if (p[0] == 'width') { width = parseInt(p[1]); }
if (p[0] == 'height') { height = parseInt(p[1]); }
var p = inputs.pop();
if (p[0] == 'width') { width = parseInt(p[1]); }
if (p[0] == 'height') { height = parseInt(p[1]); }
// don't add type=line to querystring, it's the default value
if (!(p[0] == 'type' && p[1] == 'line')) {
qs += '&' + p[0] + '=' + encodeURIComponent(p[1]);
}
// don't add type=line to querystring, it's the default value
if (!(p[0] == 'type' && p[1] == 'line')) {
qs += '&' + p[0] + '=' + encodeURIComponent(p[1]);
}
}
// if querystring exists, add it to src
if (qs.length > 0) { src += '?' + qs.substring(1); }
// save chart options to database
if (postUpdate && index > 0 && newOptionsSave) {
$.update("/channels/" + channelId + "/charts/" + index,
{
newOptions : { options: qs }
} );
$.update("/channels/" + channelId + "/charts/" + index,
{
newOptions : { options: qs }
} );
}
else if (postUpdate && index > 0) {
$.update("/channels/" + channelId + "/charts/" + index,
{ options: qs } );
$.update("/channels/" + channelId + "/charts/" + index,
{ options: qs } );
}
// set embed code
@ -90,9 +90,9 @@ function updateSelectValues() {
function setupChartForm(channelIndex) {
return function(index, value) {
if (value.length > 0) {
$('#' + value.split('=')[0] + "_" + channelIndex).val(decodeURIComponent(value.split('=')[1]));
}
if (value.length > 0) {
$('#' + value.split('=')[0] + "_" + channelIndex).val(decodeURIComponent(value.split('=')[1]));
}
};
}
@ -107,52 +107,52 @@ function setupColumns(current_user, channel_id)
function createWindowsWithData (data, current_user, channel_id, colName) {
for (var i in data) {
//each array element has a single chart object as an associative array with the type as the key
// so I need to iterate over a array with size=1 to get a string with the window type
for (var type in data[i]) {
var wtype = type;
}
if (data[i].chart_window) window = data[i].chart_window;
if (data[i].plugin_window) window = data[i].plugin_window;
if (data[i].portlet_window) window = data[i].portlet_window;
for (var i in data) {
//each array element has a single chart object as an associative array with the type as the key
// so I need to iterate over a array with size=1 to get a string with the window type
for (var type in data[i]) {
var wtype = type;
}
if (data[i].chart_window) window = data[i].chart_window;
if (data[i].plugin_window) window = data[i].plugin_window;
if (data[i].portlet_window) window = data[i].portlet_window;
if (window == "undefined")
if (window == "undefined")
var window = (data[i].portlet_window) ? data[i].portlet_window : data[i].chart_window;
colId = window.col;
title = window.title;
colId = window.col;
title = window.title;
var content = window.html;
if (data[i].chart_window) {
var windowId = window.id;
$("body").append("<div id='chartConfig"+windowId+"'></div>");
var content = window.html;
if (data[i].chart_window) {
var windowId = window.id;
$("body").append("<div id='chartConfig"+windowId+"'></div>");
}
var portlet = addWindow(colName, colId, window.id, wtype, title, content);
portlet.each ( decoratePortlet(current_user) ) ;
var portlet = addWindow(colName, colId, window.id, wtype, title, content);
portlet.each ( decoratePortlet(current_user) ) ;
portlet.find( ".ui-toggle" ).click( uiToggleClick );
portlet.find( ".ui-view" ).click( uiViewClick (channel_id) );
portlet.find( ".ui-edit" ).click( uiEditClick (channel_id) );
portlet.find( ".ui-close" ).click( uiCloseClick (channel_id) );
}
portlet.find( ".ui-toggle" ).click( uiToggleClick );
portlet.find( ".ui-view" ).click( uiViewClick (channel_id) );
portlet.find( ".ui-edit" ).click( uiEditClick (channel_id) );
portlet.find( ".ui-close" ).click( uiCloseClick (channel_id) );
}
}
var createWindows = function (current_user, channel_id, colName) {
return function(data) {
createWindowsWithData(data, current_user, channel_id, colName);
createWindowsWithData(data, current_user, channel_id, colName);
};
}
function addWindow(colName, colId, windowId, wtype, title, content) {
$("#"+colName+"_dialog"+colId).append('<div class="portlet ui-widget ui-widget-content ui-helper-clearfix ui-corner-all" ' +
'id="portlet_' + windowId +
'"><div class="portlet-header wtype wtype-'+ wtype
+ ' ui-widget-header ui-corner-all">' + title +
'</div><div class="portlet-content">'+content+'</div>') ;
'id="portlet_' + windowId +
'"><div class="portlet-header wtype wtype-'+ wtype
+ ' ui-widget-header ui-corner-all">' + title +
'</div><div class="portlet-content">'+content+'</div>') ;
if ($("#portlet_"+windowId).length > 1) {
throw "Portlet count doesn't match what's expected";
throw "Portlet count doesn't match what's expected";
} else {
return $("#portlet_"+windowId);
return $("#portlet_"+windowId);
}
}
@ -161,22 +161,22 @@ function addWindow(colName, colId, windowId, wtype, title, content) {
var updatePortletPositions = function( current_user, channel_id) {
return function() {
if (current_user) {
var result = $(this).sortable('serialize');
colId = $(this).attr('id').charAt($(this).attr('id').length - 1);
portletArray = getPortletArray(result);
jsonResult = {
"col" : colId,
"positions" : portletArray
} ;
var result = $(this).sortable('serialize');
colId = $(this).attr('id').charAt($(this).attr('id').length - 1);
portletArray = getPortletArray(result);
jsonResult = {
"col" : colId,
"positions" : portletArray
} ;
if (portletArray.length > 0) {
$.ajax({
type: 'PUT',
url: '../channels/' + channel_id + '/windows',
data: {_method:'PUT', page : JSON.stringify(jsonResult ) },
dataType: 'json'
});
}
if (portletArray.length > 0) {
$.ajax({
type: 'PUT',
url: '../channels/' + channel_id + '/windows',
data: {_method:'PUT', page : JSON.stringify(jsonResult ) },
dataType: 'json'
});
}
}
}
}
@ -184,31 +184,31 @@ var updatePortletPositions = function( current_user, channel_id) {
function sortColumnSetup(current_user, channel_id) {
$( ".column" ).sortable({
opacity: 0.6,
helper: function( event ) {
return $("<div class='ui-widget-header'>Drop to re-position</div>");
},
connectWith: ".column",
update: updatePortletPositions(current_user, channel_id)
});
opacity: 0.6,
helper: function( event ) {
return $("<div class='ui-widget-header'>Drop to re-position</div>");
},
connectWith: ".column",
update: updatePortletPositions(current_user, channel_id)
});
}
var decoratePortlet = function (current_user) {
return function() {
var portletHeader = $(this).find( ".portlet-header") ;
portletHeader.append( "<span id='commentBtn' class='ui-view ui-icon ui-icon-comment'></span>");
var portletHeader = $(this).find( ".portlet-header") ;
portletHeader.append( "<span id='commentBtn' class='ui-view ui-icon ui-icon-comment'></span>");
thisObject = $(this);
if (current_user == "true") {
// Use feature Rollout here - needs to be implemented for this user, and this channel needs to belong to this user.
thisObject.find('.wtype').prepend( "<span id='minusBtn' class='ui-toggle ui-icon ui-icon-minusthick'></span>");
thisObject.find(".wtype-chart_window").append("<span id='pencilBtn' class='ui-edit ui-icon ui-icon-pencil'></span>");
thisObject.find(".wtype").append("<span id='closeBtn' class='ui-close ui-icon ui-icon-close'></span>");
thisObject.find(".portlet-header").css("cursor","move");
}
else {
$(".column").sortable({ disabled:true });
}
return $(this).attr("id");
thisObject = $(this);
if (current_user == "true") {
// Use feature Rollout here - needs to be implemented for this user, and this channel needs to belong to this user.
thisObject.find('.wtype').prepend( "<span id='minusBtn' class='ui-toggle ui-icon ui-icon-minusthick'></span>");
thisObject.find(".wtype-chart_window").append("<span id='pencilBtn' class='ui-edit ui-icon ui-icon-pencil'></span>");
thisObject.find(".wtype").append("<span id='closeBtn' class='ui-close ui-icon ui-icon-close'></span>");
thisObject.find(".portlet-header").css("cursor","move");
}
else {
$(".column").sortable({ disabled:true });
}
return $(this).attr("id");
}
}
function getPortletArray(data) {
@ -218,8 +218,8 @@ function getPortletArray(data) {
for (i in inputArray) {
val = inputArray[i].split("=")[1] ;
resultArray.push(val);
val = inputArray[i].split("=")[1] ;
resultArray.push(val);
}
return resultArray;
@ -228,63 +228,63 @@ function getPortletArray(data) {
var uiEditClick = function (channel_id) {
return function() {
var id = $( this ).parents( ".portlet:first" ).attr("id").substring(8);
var id = $( this ).parents( ".portlet:first" ).attr("id").substring(8);
var options = "";
$("#chartConfig"+id).load("/channels/"+channel_id+"/charts/"+id+"/edit",
function() {
options = $("#chartOptions"+id).html();
var options = "";
$("#chartConfig"+id).load("/channels/"+channel_id+"/charts/"+id+"/edit",
function() {
options = $("#chartOptions"+id).html();
if (options != "undefined" && options.length >2) {
$.each((options.split('&amp;')), setupChartForm( id ));
}
$("#button"+id).click( function() {
updateChart(id, true, 450, 250, channel_id, true);
$("#chartConfig"+id).dialog("close");
if (options != "undefined" && options.length >2) {
$.each((options.split('&amp;')), setupChartForm( id ));
}
$("#button"+id).click( function() {
updateChart(id, true, 450, 250, channel_id, true);
$("#chartConfig"+id).dialog("close");
});
})
.dialog({ title:"Chart Options", modal: true, resizable: false, width: 500, dialogClass: "dev-info-dialog" });
});
})
.dialog({ title:"Chart Options", modal: true, resizable: false, width: 500, dialogClass: "dev-info-dialog" });
};
}
var uiViewClick = function (channel_id) {
return function() {
var x = $( this ).parents( ".portlet:first" ).find( ".portlet-content" ).offset().left;
var y = $( this ).parents( ".portlet:first" ).find( ".portlet-content" ).offset().top;
var id = $( this ).parents( ".portlet:first" ).attr("id").substring(8);
var x = $( this ).parents( ".portlet:first" ).find( ".portlet-content" ).offset().left;
var y = $( this ).parents( ".portlet:first" ).find( ".portlet-content" ).offset().top;
var id = $( this ).parents( ".portlet:first" ).attr("id").substring(8);
$("body").append('<div id="iframepopup'+id+'" style="display:none">' +
'<div id="iframeinner'+id+'"style="font-size:1.2em;overflow:auto;height:115px;background-color:white">' +
'</div></div>');
$("body").append('<div id="iframepopup'+id+'" style="display:none">' +
'<div id="iframeinner'+id+'"style="font-size:1.2em;overflow:auto;height:115px;background-color:white">' +
'</div></div>');
$.get("/channels/"+channel_id+"/windows/"+id+"/iframe",
function(response) {
var display = response.replace(/id=\"iframe[0-9]?[0-9]?[0-9]?[0-9]?[0-9]?[0-9]?[0-9]?[0-9]?[0-9]?[0-9]?\"/, "" );
$("#iframeinner"+id).text(display);
}
);
$.get("/channels/"+channel_id+"/windows/"+id+"/iframe",
function(response) {
var display = response.replace(/id=\"iframe[0-9]?[0-9]?[0-9]?[0-9]?[0-9]?[0-9]?[0-9]?[0-9]?[0-9]?[0-9]?\"/, "" );
$("#iframeinner"+id).text(display);
}
);
$("#iframepopup"+id).dialog({
resizable:false,
width: "300px",
position:[x+200,y-200],
title: "Chart Iframe",
dialogClass: "dev-info-dialog"
});
$("#iframepopup"+id).dialog({
resizable:false,
width: "300px",
position:[x+200,y-200],
title: "Chart Iframe",
dialogClass: "dev-info-dialog"
});
};
}
var uiCloseClick = function (channel_id) {
return function() {
var id = $( this ).parents( ".portlet:first" ).attr("id").substring(8);
var portlet = $( this ).parents( ".portlet:first" ) ;
$.update("/channels/"+channel_id+"/windows/"+id+"/hide" ,
function(response) {
portlet.hide("drop", function(){
portlet.remove();});
}) ;
var id = $( this ).parents( ".portlet:first" ).attr("id").substring(8);
var portlet = $( this ).parents( ".portlet:first" ) ;
$.update("/channels/"+channel_id+"/windows/"+id+"/hide" ,
function(response) {
portlet.hide("drop", function(){
portlet.remove();});
}) ;
}
}

View File

@ -0,0 +1,17 @@
// SASS variable overrides must be declared before loading up Active Admin's styles.
//
// To view the variables that Active Admin provides, take a look at
// `app/assets/stylesheets/active_admin/mixins/_variables.css.scss` in the
// Active Admin source.
//
// For example, to change the sidebar width:
// $sidebar-width: 242px;
// Active Admin's got SASS!
@import "active_admin/mixins";
@import "active_admin/base";
// Overriding any non-variable SASS must be done after the fact.
// For example, to change the default status-tag color:
//
// .status_tag { background: #6090DB; }

View File

@ -7,6 +7,7 @@
body { padding-top: 70px; }
.break-word { word-break: break-word; }
.col-pad { padding: 0 15px; }
.dismiss { float: right; cursor: pointer; position: relative; top: -12px; left: 7px; }
/* multiline forms */
.form-horizontal .multiline-label { margin-top: -10px; }
@ -29,7 +30,9 @@ body { padding-top: 70px; }
.format-xml { display: none; }
.format-block { min-height: 200px; }
.format-block-lg { min-height: 350px; }
.format-block-xl { min-height: 400px; }
.format-block-xxl { min-height: 600px; }
.format-block-xxxl { min-height: 1000px; }
/* Sticky footer styles
-------------------------------------------------- */
@ -70,7 +73,6 @@ body {
.apps a:hover,
.apps:hover div { text-decoration: none; }
.commentarea { width: 300px; height: 80px; }
#options { float: right; text-align: right; }
#login {
padding: 6px;
border: 1px solid #bbbbbb;
@ -228,10 +230,9 @@ input[type="text"].midfield { width: 120px; }
textarea.tweet { margin-top: 0.5em; width: 40em; height: 3em; }
/* error messages */
.errorExplanation { width: 95%; background-color: #ffffe0; display: table; margin-bottom: 20px; padding: 10px; border: 1px solid #aaaaaa; }
#error {
color: red;
}
.errorExplanation,
#error_explanation { padding: 15px; margin-bottom: 20px; border: 1px solid transparent; border-radius: 4px; background-color: #f2dede; border-color: #ebccd1; color: #a94442; }
#error { color: red; }
.field_with_errors { display: inline; }
/*.error_box { margin-top: 15px; padding: 5px; background-color: #f99; color: #300; border: 1px solid #f66; }*/
.warning_box { margin: 15px 0 15px 0; padding: 10px; background-color: #fc3; color: #000; border: 1px solid #f90; }

View File

@ -7,6 +7,13 @@
border: 1px solid #bbb;
border-radius: 5px;
padding: 2px 0;
margin-top: 20px;
width: 228px;
}
#bootstrap-sidebar li a {
padding: 1px 8px;
font-size: 15px;
}
#bootstrap-sidebar li a:hover {
@ -18,11 +25,23 @@
border-right-width:4px;
}
#bootstrap-sidebar li.subitem a {
padding-left: 30px;
font-size: 12px;
}
@media (min-width: 979px) {
#bootstrap-sidebar.affix-top, #bootstrap-sidebar.affix {
position: fixed;
top:90px;
width:228px;
width: 228px;
margin-top: 0;
}
}
@media (max-width: 978px) {
#bootstrap-sidebar.affix-top, #bootstrap-sidebar.affix {
width: 100%;
}
}

View File

@ -1,11 +1,14 @@
class ApplicationController < ActionController::Base
skip_before_filter :verify_authenticity_token
# include all helpers for controllers
helper :all
# include these helper methods for views
helper_method :current_user_session, :current_user, :logged_in?, :is_admin?, :get_header_value, :to_bytes
helper_method :current_user_session, :current_user, :logged_in?, :get_header_value, :to_bytes
protect_from_forgery
before_filter :allow_cross_domain_access, :set_variables
before_filter :configure_permitted_parameters, if: :devise_controller?
after_filter :remove_headers
before_filter :authenticate_user_from_token!
# responds with blank
def respond_with_blank
@ -33,8 +36,24 @@ class ApplicationController < ActionController::Base
@ssl_api_domain ||= ssl_api_domain
@locale ||= get_locale
I18n.locale = @locale
# sets timezone for current user, all DateTime outputs will be automatically formatted
Time.zone = current_user.time_zone if current_user
# allows use of daily params
params[:timescale] = '1440' if params[:timescale] == 'daily'
params[:average] = '1440' if params[:average] == 'daily'
params[:median] = '1440' if params[:median] == 'daily'
params[:sum] = '1440' if params[:sum] == 'daily'
end
# change default devise sign_in page; make admins sign in work correctly
def after_sign_in_path_for(resource)
if resource.is_a?(AdminUser)
admin_dashboard_path
else
channels_path
end
end
# get the locale, but don't fail if header value doesn't exist
@ -52,8 +71,30 @@ class ApplicationController < ActionController::Base
return locale
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:login, :email, :password, :password_confirmation, :remember_me) }
devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:login, :email, :password, :remember_me) }
devise_parameter_sanitizer.for(:account_update) { |u| u.permit(:login, :email, :password, :password_confirmation, :time_zone, :password_current) }
end
private
# authenticates user based on token from users#api_login
def authenticate_user_from_token!
# exit if no login or token
return false if params[:login].blank? || params[:token].blank?
# get the user by login or email
user = User.find_by_login_or_email(params[:login])
# safe compare, avoids timing attacks
if user.present? && Devise.secure_compare(user.authentication_token, params[:token])
sign_in user, store: false
end
end
# remove headers if necessary
def remove_headers
response.headers.delete_if {|key| true} if params[:headers] == 'false'
@ -71,15 +112,6 @@ class ApplicationController < ActionController::Base
true if current_user
end
# check that user's email address matches admin
def is_admin?
current_user && ADMIN_EMAILS.include?(current_user.email)
end
def set_admin_menu
@menu = 'admin'
end
# converts a string to a byte string for c output
def to_bytes(input, separator='.', prefix='')
return '' if input == nil
@ -90,35 +122,16 @@ class ApplicationController < ActionController::Base
return output.join(', ')
end
def set_channels_menu
@menu = 'channels'
end
def set_apps_menu
@menu = 'apps'
end
def set_plugins_menu
@menu = 'plugins'
end
def set_devices_menu
@menu = 'devices'
end
def current_user_session
return @current_user_session if defined?(@current_user_session)
@current_user_session = UserSession.find
end
def current_user
return @current_user if defined?(@current_user)
@current_user = current_user_session && current_user_session.record
end
# set menus
def set_support_menu; @menu = 'support'; end
def set_channels_menu; @menu = 'channels'; end
def set_apps_menu; @menu = 'apps'; end
def set_plugins_menu; @menu = 'plugins'; end
def set_devices_menu; @menu = 'devices'; end
def require_user
logger.info "Require User"
if current_user.nil?
if current_user.nil? && User.find_by_api_key(get_apikey).nil?
respond_to do |format|
format.html {
session[:link_back] = request.url
@ -144,7 +157,7 @@ class ApplicationController < ActionController::Base
end
def require_admin
unless current_user && is_admin?
unless current_admin_user.present?
render :nothing => true, :status => 403 and return
false
end
@ -178,8 +191,10 @@ class ApplicationController < ActionController::Base
end
# domain for the api
def api_domain
(Rails.env == 'production') ? API_DOMAIN : domain
def api_domain(ssl=false)
output = (Rails.env == 'production') ? API_DOMAIN : domain
output = output.sub(/http:/, 'https:') if ssl == true
return output
end
# ssl domain for the api
@ -275,9 +290,6 @@ class ApplicationController < ActionController::Base
# options: days = how many days ago, start = start date, end = end date, offset = timezone offset
def get_date_range(params)
# set timezone correctly
set_time_zone(params)
# allow more past data if necessary
get_old_data = (params[:results].present? || params[:start].present? || params[:days].present?) ? true : false
@ -292,8 +304,6 @@ class ApplicationController < ActionController::Base
return date_range
end
def set_time_zone(params)
# set timezone correctly
if params[:offset]

View File

@ -3,7 +3,7 @@ class ChannelsController < ApplicationController
before_filter :require_user, :except => [ :show, :post_data, :social_show, :social_feed, :public]
before_filter :set_channels_menu
layout 'application', :except => [:social_show, :social_feed]
protect_from_forgery :except => :post_data
protect_from_forgery :except => [:post_data, :create, :destroy, :clear]
require 'csv'
# view list of watched channels
@ -29,17 +29,27 @@ class ChannelsController < ApplicationController
# list public channels
def public
@domain = domain
# default blank response
@channels = Channel.where(:id => 0).paginate :page => params[:page]
# get channels by ids
if params[:channel_ids].present?
flash[:notice] = t(:selected_channels)
@header = t(:selected_channels)
@channels = Channel.public_viewable.by_array(params[:channel_ids]).order('ranking desc, updated_at DESC').paginate :page => params[:page]
# get channels that match a user
elsif params[:username].present?
@header = "#{t(:user).capitalize}: #{params[:username]}"
searched_user = User.find_by_login(params[:username])
@channels = searched_user.channels.public_viewable.active.order('ranking desc, updated_at DESC').paginate :page => params[:page] if searched_user.present?
# get channels that match a tag
elsif params[:tag].present?
flash[:notice] = "#{t(:tag).capitalize}: #{params[:tag]}"
@header = "#{t(:tag).capitalize}: #{params[:tag]}"
@channels = Channel.public_viewable.active.order('ranking desc, updated_at DESC').with_tag(params[:tag]).paginate :page => params[:page]
# normal channel list
else
flash[:notice] = t(:featured_channels)
@header = t(:featured_channels)
respond_with_error(:error_resource_not_found) and return if params[:page] == '0'
@channels = Channel.public_viewable.active.order('ranking desc, updated_at DESC').paginate :page => params[:page]
end
@ -119,11 +129,10 @@ class ChannelsController < ApplicationController
end
def index
@channels = current_user.channels
respond_to do |format|
format.html
format.json { render :json => @channels }
format.json { render :json => @channels.to_json(:root => false) }
end
end
@ -180,29 +189,52 @@ class ChannelsController < ApplicationController
flash[:notice] = t(:channel_update_success)
redirect_to channel_path(@channel.id)
end
def create
channel = current_user.channels.create(:field1 => "#{t(:channel_default_field)} 1")
# get the current user or find the user via their api key
@user = current_user || User.find_by_api_key(get_apikey)
channel = @user.channels.create(:field1 => "#{t(:channel_default_field)} 1")
# make updating attributes easier
params[:channel] = params
channel.update_attributes(channel_params)
channel.set_windows
channel.save
channel.save_tags(params[:channel][:tags]) if params[:channel][:tags].present?
channel.add_write_api_key
@channel_id = channel.id
redirect_to channel_path(@channel_id, :anchor => "channelsettings")
respond_to do |format|
format.json { render :json => channel.to_json(Channel.private_options) }
format.xml { render :xml => channel.to_xml(Channel.private_options) }
format.any { redirect_to channel_path(@channel_id, :anchor => "channelsettings") }
end
end
# clear all data from a channel
def clear
channel = current_user.channels.find(params[:id])
# get the current user or find the user via their api key
@user = current_user || User.find_by_api_key(get_apikey)
channel = @user.channels.find(params[:id])
channel.delete_feeds
redirect_to channel_path(channel.id)
respond_to do |format|
format.json { render :json => [] }
format.xml { render :xml => [] }
format.any { redirect_to channel_path(channel.id) }
end
end
def destroy
channel = current_user.channels.find(params[:id])
channel.destroy
redirect_to channels_path
# get the current user or find the user via their api key
@user = current_user || User.find_by_api_key(get_apikey)
@channel = @user.channels.find(params[:id])
@channel.destroy
respond_to do |format|
format.json { render :json => @channel.to_json(Channel.public_options) }
format.xml { render :xml => @channel.to_xml(Channel.public_options) }
format.any { redirect_to channels_path, :status => 303 }
end
end
# response is '0' if failure, 'entry_id' if success
@ -311,8 +343,13 @@ class ChannelsController < ApplicationController
# if there is a talkback_key but no command
respond_with_blank and return if params[:talkback_key].present? && command.blank?
# normal route, respond with the entry id of the feed
render :text => status
# normal route, respond with the feed
respond_to do |format|
format.html { render :text => status }
format.json { render :json => feed.to_json }
format.xml { render :xml => feed.to_xml(Feed.public_options) }
format.any { render :text => status }
end and return
end
# import view

View File

@ -55,8 +55,8 @@ class ChartsController < ApplicationController
params[:bgcolor] = fix_color(params[:bgcolor])
# set ssl
@ssl = (get_header_value('x_ssl') == 'true')
@domain = domain(@ssl)
ssl = (get_header_value('x_ssl') == 'true')
@domain = domain(ssl)
# should data be pushed off the end in dynamic chart
@push = (params[:push] and params[:push] == 'false') ? false : true

View File

@ -16,7 +16,7 @@ class CommentsController < ApplicationController
@comment.body = params[:comment][:body].gsub(/<\/?[^>]*>/, '').gsub(/\n/, '<br />')
# save comment
if @comment.save
flash[:success] = "Thanks for adding a comment!"
flash[:notice] = "Thanks for adding a comment!"
else
flash[:error] = "Comment can't be blank!"
end
@ -45,3 +45,4 @@ class CommentsController < ApplicationController
redirect_to :back
end
end

View File

@ -1,6 +1,45 @@
class DocsController < ApplicationController
before_filter :set_support_menu
def index; ;end
def index; ; end
def errors; ; end
def tweetcontrol; ; end
def plugins; ; end
def importer; ; end
def charts; ; end
def users; ; end
def channels
# default values
@channel_api_key = 'XXXXXXXXXXXXXXXX'
@user_api_key = 'XXXXXXXXXXXXXXXX'
# if user is signed in
if current_user && current_user.channels.any?
@channel_api_key = current_user.channels.order('updated_at desc').first.write_api_key
@user_api_key = current_user.api_key
end
end
def thinghttp
# default values
@thinghttp_api_key = 'XXXXXXXXXXXXXXXX'
# if user is signed in
if current_user && current_user.thinghttps.any?
@thinghttp_api_key = current_user.thinghttps.order('updated_at desc').first.api_key
end
end
def thingtweet
# default values
@thingtweet_api_key = 'XXXXXXXXXXXXXXXX'
# if user is signed in
if current_user && current_user.twitter_accounts.any?
@thingtweet_api_key = current_user.twitter_accounts.order('updated_at desc').first.api_key
end
end
def talkback
# default values

View File

@ -194,13 +194,13 @@ class FeedController < ApplicationController
output = @feed.to_xml
elsif params[:format] == 'csv'
@csv_headers = Feed.select_options(@channel, params)
elsif (params[:format] == 'txt' or params[:format] == 'text')
elsif (params[:format] == 'txt' || params[:format] == 'text' || params[:format] == 'html' || params[:format].blank?)
output = add_prepend_append(@feed["field#{params[:field_id]}"])
else
output = @feed.to_json
end
# else set error code
# else set error code
else
if params[:format] == 'xml'
output = bad_feed_xml
@ -211,7 +211,7 @@ class FeedController < ApplicationController
# output data in proper format
respond_to do |format|
format.html { render :json => output }
format.html { render :text => output }
format.json { render :json => output, :callback => params[:callback] }
format.xml { render :xml => output }
format.csv

View File

@ -0,0 +1,16 @@
class SessionsController < Devise::SessionsController
before_filter :fix_params, :only => :create
# don't modify default devise controllers
def create; super; end
def new; super; end
private
# fixes password reset params so that devise config.reset_password_keys can be set to email for activeadmin
def fix_params
params[:user][:login] = params[:user][:email]
end
end

View File

@ -1,5 +1,5 @@
class PipesController < ApplicationController
before_filter :require_admin, :set_admin_menu
before_filter :require_admin
def index
@pipes = Pipe.paginate :page => params[:page], :order => 'created_at DESC'
@ -14,3 +14,4 @@ class PipesController < ApplicationController
end
end

View File

@ -0,0 +1,22 @@
class RegistrationsController < Devise::RegistrationsController
include KeyUtilities
after_filter :add_api_key, :only => :create
# use defaults from devise
def new; super; end
def edit; super; end
def create; super; end
private
# adds an api key to the new user
def add_api_key
@user = current_user
if @user.present?
@user.api_key = generate_api_key(16, 'user')
@user.save
end
end
end

View File

@ -0,0 +1,32 @@
class SessionsController < Devise::SessionsController
after_filter :log_failed_login, :only => :new
# don't modify default devise controllers
def create; super; end
def new; super; end
private
# logs failed login attempts
def log_failed_login
if failed_login?
# log to failedlogins
failed = Failedlogin.new
failed.login = params['user']['login']
failed.password = params['user']['password']
failed.ip_address = get_header_value('X_REAL_IP')
failed.save
# prevent timing and brute force password attacks
sleep 1
end
end
# true if a login fails
def failed_login?
options = env["warden.options"]
return (options.present? && options[:action] == "unauthenticated")
end
end

View File

@ -0,0 +1,83 @@
class StreamController < ApplicationController
include ActionController::Live
require 'csv'
def channel_feed
channel = Channel.find(params[:id])
api_key = ApiKey.find_by_api_key(get_apikey)
# set timezone correctly
set_time_zone(params)
# output proper http response if error
render :text => '-1', :status => 400 and return if !channel_permission?(channel, api_key)
# set the attachment headers
response.headers['Content-Type'] = 'text/csv'
response.headers['Content-Disposition'] = 'attachment; filename=feeds.csv'
# get the feed headers
csv_headers = Feed.select_options(channel, params)
# set the total records and batch size
total_records = channel.feeds.count
batch = 1000
# write the headers row
response.stream.write "#{CSV.generate_line(csv_headers)}"
# for every 1000 records
(0..(total_records - batch).abs).step(batch) do |i|
# variable to hold the streaming output for this batch
batch_output = ""
# feeds query
feeds = Feed.where(:channel_id => channel.id).order('entry_id asc').offset(i).limit(batch)
# for each feed, add the data according to the csv_headers
feeds.each do |feed|
row = []
csv_headers.each { |attr| row.push(feed.send(attr)) }
batch_output += CSV.generate_line(row)
end
# write the output for this batch
response.stream.write batch_output
# add a slight delay between database queries
sleep 0.1
end
ensure
response.stream.close
end
def stream_example
# get the channel
channel = Channel.find(params[:channel_id])
# stream the response
response.headers['Content-Type'] = 'text/csv'
response.headers['Content-Disposition'] = 'attachment; filename=feeds.csv'
20.times {
response.stream.write "hello world\n"
sleep 1
}
ensure
response.stream.close
end
def stream_chunked_example
#response.headers['Content-Type'] = 'text/event-stream'
response.headers['Content-Type'] = 'text/csv'
response.headers['Content-Disposition'] = 'attachment; filename=feeds.csv'
response.headers['Transfer-Encoding'] = 'chunked'
10.times {
response.stream.write "4\n" # size must be in hex format?
response.stream.write "hel\n\n"
sleep 1
}
response.stream.write "0\n\n"
ensure
response.stream.close
end
end

View File

@ -1,55 +0,0 @@
class UserSessionsController < ApplicationController
before_filter :require_no_user, :only => [:new, :create]
before_filter :require_user, :only => :destroy
def new
@title = t(:signin)
@user_session = UserSession.new
@mail_message = session[:mail_message] if !session[:mail_message].nil?
end
def show
redirect_to root_path
end
def create
if params[:userlogin].length > 0
render :text => ''
else
@user_session = UserSession.new(params[:user_session])
# remember user_id if checkbox is checked
if params[:user_session][:remember_id] == '1'
cookies['user_id'] = { :value => params[:user_session][:login], :expires => 1.month.from_now }
else
cookies.delete 'user_id'
end
if @user_session.save
# if link_back, redirect back
redirect_to session[:link_back] and return if session[:link_back]
redirect_to channels_path and return
else
# log to failedlogins
failed = Failedlogin.new
failed.login = params[:user_session][:login]
failed.password = params[:user_session][:password]
failed.ip_address = get_header_value('X_REAL_IP')
failed.save
# prevent timing and brute force password attacks
sleep 1
@failed = true
render :action => :new
end
end
end
def destroy
session[:link_back] = nil
current_user_session.destroy
reset_session
redirect_to root_path
end
end

View File

@ -1,7 +1,37 @@
class UsersController < ApplicationController
include KeyUtilities
before_filter :require_no_user, :only => [:new, :create, :forgot_password]
before_filter :require_user, :only => [:show, :edit, :update, :change_password, :edit_profile]
skip_before_filter :verify_authenticity_token, :only => [:api_login]
before_filter :require_user, :only => [:show, :edit, :update, :edit_profile]
# delete account
def destroy
user = current_user
user.delete
flash[:notice] = t(:account_deleted)
redirect_to root_path
end
# allow login via api
def api_login
# get the user by login or email
user = User.find_by_login_or_email(params[:login])
# exit if no user or invalid password
respond_with_error(:error_auth_required) and return if user.blank? || !user.valid_password?(params[:password])
# save new authentication token
if user.authentication_token.blank?
user.authentication_token = Devise.friendly_token
user.save
end
# output the user with token
respond_to do |format|
format.json { render :json => user.as_json(User.private_options_plus(:authentication_token)) }
format.xml { render :xml => user.to_xml(User.private_options_plus(:authentication_token)) }
format.any { render :text => user.authentication_token }
end
end
# generates a new api key
def new_api_key
@ -34,8 +64,8 @@ class UsersController < ApplicationController
# if a json or xml request
if request.format == :json || request.format == :xml
# authenticate the user if api key matches the target user
authenticated = (User.find_by_api_key(get_apikey) == @user)
# authenticate the user if the user is logged in (can be via token) or api key matches the target user
authenticated = (current_user == @user) || (User.find_by_api_key(get_apikey) == @user)
# set options correctly
options = authenticated ? User.private_options : User.public_options(@user)
end
@ -75,27 +105,6 @@ class UsersController < ApplicationController
end
end
def new
@title = t(:signup)
@user = User.new
end
def create
@user = User.new(user_params)
@user.api_key = generate_api_key(16, 'user')
# save user
if @user.valid?
if @user.save
redirect_back_or_default channels_path and return
end
else
render :action => :new
end
end
def show
@menu = 'account'
@user = @current_user
@ -106,48 +115,18 @@ class UsersController < ApplicationController
@user = @current_user
end
# displays forgot password page
def forgot_password
@user = User.new
end
# this action is called from an email link when a password reset is requested
def reset_password
# if user has been logged in (due to previous form submission)
if !current_user.nil?
@user = current_user
@user.errors.add(:base, t(:password_problem))
@valid_link = true
else
@user = User.find_by_id(params[:id])
# make sure tokens match and password reset is within last 10 minutes
if @user.perishable_token == params[:token] && @user.updated_at > 600.seconds.ago
@valid_link = true
# log the user in
@user_session = UserSession.new(@user)
@user_session.save
end
end
end
# do the actual password change
def change_password
@user = current_user
# if no password entered, redirect
redirect_to reset_password_path and return if params[:user][:password].empty?
# check current password and update
if @user.update_attributes(user_params)
redirect_to account_path
else
redirect_to reset_password_path
end
end
def update
@menu = 'account'
@user = @current_user # makes our views "cleaner" and more consistent
# delete password and confirmation from params if not present
params[:user].delete(:password) if params[:user][:password].blank?
# check current password and update
if @user.valid_password?(params[:password_current]) && @user.update_attributes(user_params)
if @user.valid_password?(params[:user][:password_current]) && @user.update_attributes(user_params)
# sign the user back in, since devise will log the user out on update
sign_in(current_user, :bypass => true)
flash[:notice] = t('devise.registrations.updated')
redirect_to account_path
else
@user.errors.add(:base, t(:password_incorrect))

View File

@ -0,0 +1,2 @@
module AdminHelper
end

18
app/models/admin_user.rb Normal file
View File

@ -0,0 +1,18 @@
class AdminUser < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessor :login
protected
def self.find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
login = conditions.delete(:login)
where(conditions).where(["lower(email) = :value", { :value => login.strip.downcase }]).first
end
end

View File

@ -54,10 +54,11 @@ class Channel < ActiveRecord::Base
has_many :feeds
has_many :daily_feeds
has_many :api_keys, :dependent => :destroy
has_many :taggings
has_many :taggings, :dependent => :destroy
has_many :tags, :through => :taggings
has_many :comments, :dependent => :destroy
has_many :windows, :dependent => :destroy, :autosave => true
accepts_nested_attributes_for :tags
self.include_root_in_json = true
@ -81,6 +82,18 @@ class Channel < ActiveRecord::Base
cattr_reader :per_page
@@per_page = 15
# how often the channel is updated
def update_rate
last_feeds = self.feeds.order('entry_id desc').limit(2)
rate = (last_feeds.first.created_at - last_feeds.last.created_at) if last_feeds.length == 2
return rate
end
# write key for a channel
def write_api_key
self.api_keys.where(:write_flag => true).first.api_key
end
# select options
def select_options
only = [:name, :created_at, :updated_at, :id, :last_entry_id]
@ -159,6 +172,19 @@ class Channel < ActiveRecord::Base
}
end
# used when creating a channel
def self.private_options
{
:root => false,
:only => [:id, :name, :description, :latitude, :longitude, :last_entry_id, :elevation, :created_at, :ranking],
:methods => :username,
:include => {
:tags => {:only => [:id, :name]},
:api_keys => {:only => [:api_key, :write_flag]}
}
}
end
# login name of the user who created the channel
def username; self.user.try(:login); end
@ -321,14 +347,15 @@ class Channel < ActiveRecord::Base
end
def delete_feeds
if self.feeds.count < 1000
# if a small number of feeds or redis is not present
if self.feeds.count < 1000 || REDIS_ENABLED == false
Feed.delete_all(["channel_id = ?", self.id])
DailyFeed.delete_all(["channel_id = ?", self.id])
begin
self.update_attribute(:last_entry_id, nil)
rescue Exception => e
end
# else delete via background resque job
else
self.update_attribute(:clearing, true)
Resque.enqueue(ClearChannelJob, self.id)

View File

@ -33,6 +33,13 @@ class Feed < ActiveRecord::Base
attr_readonly :created_at
# for to_xml, return only the public attributes
def self.public_options
{
:except => [:id, :updated_at]
}
end
# only output these fields for feed
def self.select_options(channel, params)
only = [:created_at]
@ -96,7 +103,7 @@ class Feed < ActiveRecord::Base
# custom json output
def as_json(options = {})
super(options.merge(:except => [:updated_at, :id]))
super(Feed.public_options)
end
# check if a field value is a number

View File

@ -2,24 +2,27 @@
#
# Table name: users
#
# id :integer not null, primary key
# login :string(255) not null
# email :string(255) not null
# crypted_password :string(255) not null
# password_salt :string(255) not null
# persistence_token :string(255) not null
# perishable_token :string(255) not null
# current_login_at :datetime
# last_login_at :datetime
# current_login_ip :string(255)
# last_login_ip :string(255)
# created_at :datetime
# updated_at :datetime
# time_zone :string(255)
# public_flag :boolean default(FALSE)
# bio :text
# website :string(255)
# api_key :string(16)
# id :integer not null, primary key
# login :string(255) not null
# email :string(255) not null
# encrypted_password :string(255) not null
# password_salt :string(255)
# current_sign_in_at :datetime
# last_sign_in_at :datetime
# current_sign_in_ip :string(255)
# last_sign_in_ip :string(255)
# created_at :datetime
# updated_at :datetime
# time_zone :string(255)
# public_flag :boolean default(FALSE)
# bio :text
# website :string(255)
# api_key :string(16)
# reset_password_token :string(255)
# reset_password_sent_at :datetime
# remember_created_at :datetime
# sign_in_count :integer default(0), not null
# authentication_token :string(255)
#
####### NOTE #######
@ -28,21 +31,20 @@
####################
class User < ActiveRecord::Base
include KeyUtilities
has_many :channels
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable
has_many :channels, :dependent => :destroy
has_many :twitter_accounts, :dependent => :destroy
has_many :thinghttps, :dependent => :destroy
has_many :tweetcontrols, :dependent => :destroy
has_many :reacts, :dependent => :destroy
has_many :scheduled_thinghttps, :dependent => :destroy
has_many :talkbacks, :dependent => :destroy
has_many :plugins
has_many :devices
has_many :api_keys
has_many :plugins, :dependent => :destroy
has_many :devices, :dependent => :destroy
has_many :api_keys, :dependent => :destroy
has_many :watchings, :dependent => :destroy
has_many :watched_channels, :through => :watchings, :source => :channel
has_many :comments
acts_as_authentic
has_many :watched_channels, :through => :watchings, :source => :channel, :dependent => :destroy
has_many :comments, :dependent => :destroy
self.include_root_in_json = false
@ -50,6 +52,39 @@ class User < ActiveRecord::Base
cattr_reader :per_page
@@per_page = 50
# allow login by login name also
def self.find_first_by_auth_conditions(warden_conditions)
conditions = warden_conditions.dup
if login_param = conditions.delete(:login)
where(conditions).where(["lower(login) = :value OR lower(email) = :value", { :value => login_param.downcase }]).first
else
where(conditions).first
end
end
# allow users to sign in with passwords from old authlogic authentication
alias :devise_valid_password? :valid_password?
def valid_password?(password)
begin
devise_valid_password?(password)
rescue BCrypt::Errors::InvalidHash
stretches = 20
digest = "#{password}#{self.password_salt}"
stretches.times {digest = Digest::SHA512.hexdigest(digest)}
if digest == self.encrypted_password
#Here update old Authlogic SHA512 Password with new Devise ByCrypt password
# SOURCE: https://github.com/plataformatec/devise/blob/master/lib/devise/models/database_authenticatable.rb
# Digests the password using bcrypt.
self.encrypted_password = self.password_digest(password)
self.save
return true
else
# If not BCryt password and not old Authlogic SHA512 password don't authenticate user
return false
end
end
end
# find a user using login or email
def self.find_by_login_or_email(login)
User.find_by_login(login) || User.find_by_email(login)
@ -81,6 +116,12 @@ class User < ActiveRecord::Base
{ :only => [:id, :login, :created_at, :email, :website, :bio] }
end
# add an extra attribute to private_options
def self.private_options_plus(array)
{ :only => User.private_options[:only].push(array).flatten }
end
# set new api key
def set_new_api_key!
new_api_key = generate_api_key(16, 'user')
@ -90,4 +131,3 @@ class User < ActiveRecord::Base
end

View File

@ -0,0 +1,4 @@
<%= link_to 'List Email Addresses', admin_emails_path %>
<br><br>
<%= link_to 'List Users by Day (CSV)', admin_signups_path(:format => :csv) %>

View File

@ -0,0 +1,4 @@
<% @users.each do |u| %>
<%= u.email %><br>
<% end %>

View File

@ -0,0 +1,2 @@
<%= CSV.generate_line @csv_headers %><% @days.each do |day| %><% row = [] %><% @csv_headers.each do |attr| %><% row.push(day.to_json) %><% end %><%= CSV.generate_line(day).html_safe %><% end %>

View File

@ -1,11 +1,11 @@
<div class="FL">
<h3><%= t(:api_key_write) %></h3>
<%= @write_key %>
<br /><br />
<br><br>
<%= button_to t(:api_key_write_new), channel_api_keys_path(@channel, :write => 1), :data => { :confirm => t(:confirm_new_api_key) } %>
<br /><br />
<br><br>
<h3><%= t(:api_key_read) %></h3>
<% @read_keys.each do |read_key| %>
@ -29,22 +29,22 @@
<%= button_to t(:api_key_delete), channel_api_key_path(@channel, read_key), :method => 'delete', :data => { :confirm => t(:confirm_read_key_delete) } %></td>
</tr>
</table>
<br /><br />
<br><br>
<% end %>
<%= button_to t(:api_key_read_new), channel_api_keys_path(@channel, :write => 0) %>
<br />
<br>
</div>
<div id="sidebar_old">
<ul>
<li>
<div class="helplink">(<a target="_blank" href="http://community.thingspeak.com/documentation/api/#api_keys"><%= t(:help_options) %></a>)</div>
<div class="helplink">(<a target="_blank" href="/docs/channels#api_keys"><%= t(:help_options) %></a>)</div>
<h2><%= t(:help) %></h2>
<%= t(:help_channel_write_key) %>
<br /><br />
<br><br>
<%= t(:help_channel_read_key) %>
<br /><br />
<br><br>
<%= t(:help_channel_read_key_note) %>
</li>
</ul>

View File

@ -8,7 +8,7 @@
<div class="apps">
<%= link_to thingtweets_path do %>
<%= image_tag 'thingtweet.png', :size => '104x104' %>
<br />
<br>
<%= t(:thingtweet) %>
<% end %>
</div>
@ -16,7 +16,7 @@
<div class="apps">
<%= link_to thinghttp_index_path do %>
<%= image_tag 'thinghttp.png', :size => '104x104' %>
<br />
<br>
<%= t(:thinghttp) %>
<% end %>
</div>
@ -24,7 +24,7 @@
<div class="apps">
<%= link_to tweetcontrol_index_path do %>
<%= image_tag 'tweetcontrol.png', :size => '104x104' %>
<br />
<br>
<%= t(:tweetcontrol) %>
<% end %>
</div>
@ -32,7 +32,7 @@
<div class="apps">
<%= link_to reacts_path do %>
<%= image_tag 'react.png', :size => '104x104' %>
<br />
<br>
<%= t(:react) %>
<% end %>
</div>
@ -40,16 +40,16 @@
<div class="apps">
<%= link_to talkbacks_path do %>
<%= image_tag 'talkback.png', :size => '104x104' %>
<br />
<br>
<%= t(:talkback) %>
<% end %>
</div>
<% if is_admin? %>
<% if current_admin_user.present? %>
<div class="apps">
<%= link_to scheduled_thinghttps_path do %>
<%= image_tag 'scheduled_thinghttp.png', :size => '104x104' %>
<br />
<br>
<%= t(:scheduled_thinghttp) %>
<% end %>
</div>
@ -66,7 +66,7 @@
<h3>
<%= t(:thingtweet) %>
<div class="helplink">(<a href="http://community.thingspeak.com/documentation/apps/thingtweet/"><%= t(:help_options) %></a>)</div>
<div class="helplink">(<a href="/docs/thingtweet/"><%= t(:help_options) %></a>)</div>
</h3>
<%= t(:help_apps_thingtweet) %>
<ul>
@ -77,7 +77,7 @@
<h3>
<%= t(:thinghttp) %>
<div class="helplink">(<a href="http://community.thingspeak.com/documentation/apps/thinghttp/"><%= t(:help_options) %></a>)</div>
<div class="helplink">(<a href="/docs/thinghttp/"><%= t(:help_options) %></a>)</div>
</h3>
<%= t(:help_apps_thinghttp) %>
<ul>
@ -88,14 +88,14 @@
<h3>
<%= t(:tweetcontrol) %>
<div class="helplink">(<a href="http://community.thingspeak.com/documentation/apps/tweetcontrol/"><%= t(:help_options) %></a>)</div>
<div class="helplink">(<a href="/docs/tweetcontrol"><%= t(:help_options) %></a>)</div>
</h3>
<%= t(:help_apps_tweetcontrol) %>
<br><br>
<h3>
<%= t(:react) %>
<div class="helplink">(<a href="http://community.thingspeak.com/documentation/apps/react/"><%= t(:help_options) %></a>)</div>
<div class="helplink">(<a href="/docs/react"><%= t(:help_options) %></a>)</div>
</h3>
<%= t(:help_apps_react) %>
<br><br>

View File

@ -1,10 +1,5 @@
<div class="FL">
<% flash.each do |name, msg| %>
<div id="<%= name %>" class="fade" >
<p><%= msg %></p>
<div class="clear"></div>
</div>
<% end %>
<%= form_for @channel, :html => {:method => 'put'} do |c| %>
<% unless session[:errors].nil?
session[:errors].each do |attr, msg|
@ -109,12 +104,12 @@
</table>
<% end %>
<br /><br />
<br><br>
<h4><%= t(:channel_clear_message) %></h4>
<%= button_to t(:channel_clear), { :controller => 'channels', :action => 'clear', :id => @channel.id }, :data => { :confirm => t(:confirm_channel_clear) } %>
<%= button_to t(:channel_clear), "/channels/#{@channel.id}/clear", :data => { :confirm => t(:confirm_channel_clear) } %>
<br /><br />
<br><br>
<h4><%= t(:channel_delete_message) %></h4>
<%= button_to t(:channel_delete), channel_path(@channel.id), :method => 'delete', :data => { :confirm => t(:confirm_channel_delete) } %>
@ -124,17 +119,17 @@
<li>
<h2><%= t(:help) %></h2>
<%= t(:help_channel_public) %>
<br /><br />
<br><br>
<%= t(:help_channel_url) %>
<br /><br />
<br><br>
<%= t(:help_channel_video) %>
<br /><br />
<br><br>
<%= t(:help_channel_fields) %>
<br /><br />
<br><br>
<%= t(:help_channel_clear) %>
<br /><br />
<br><br>
<%= t(:help_channel_ranking) %>
<br /><br />
<br><br>
</li>
</ul>
</div>

View File

@ -1,46 +1,48 @@
<div class="FL">
<h3><%= t(:import) %></h3>
<%= t(:upload_select) %>
<br /><br />
<% flash.each do |name, msg| %>
<div id="<%= name %>" class="fade" >
<p><%= msg %></p>
<div class="clear"></div>
</div>
<% end %>
<br>
<%= form_for :upload, :url => upload_channel_path(@channel), :html => { :multipart => true } do |f| %>
<%= f.file_field :csv %>
<br /><br />
<br><br>
<%= t(:time_zone) %>
<%= time_zone_select 'feed', 'time_zone', nil, :default => 'UTC' %>
<br /><br />
<br><br>
<%= f.submit t(:upload), :disable_with => t(:uploading) %>
<% end %>
<br><br>
<h3><%= t(:export) %></h3>
<%= t(:download_feeds) %>
<br><br>
<%= button_to t(:download), "#{@ssl_api_domain}stream/channels/#{@channel.id}/feeds?api_key=#{@channel.write_api_key}" %>
</div>
<div id="sidebar_old" >
<ul>
<li>
<div class="helplink">(<a target="_blank" href="http://community.thingspeak.com/documentation/api/#send_data"><%= t(:help_options) %></a>)</div>
<div class="helplink">(<a target="_blank" href="/docs/channels#update"><%= t(:help_options) %></a>)</div>
<h2><%= t(:help_channel_update) %></h2>
<%= t(:help_channel_post) %>
<div class="code"><%= "#{@api_domain}update" %></div>
<%= t(:help_channel_post_example) %>
<div class="code"><%= "#{@api_domain}update?key=#{@key}&field1=0" %></div>
<br /><br />
<br><br>
<div class="helplink">(<a target="_blank" href="http://community.thingspeak.com/documentation/api/#view_data"><%= t(:help_options) %></a>)</div>
<div class="helplink">(<a target="_blank" href="/docs/channels#get_feed"><%= t(:help_options) %></a>)</div>
<h2><%= t(:help_channel_feed) %></h2>
<%= t(:help_channel_view) %>
<br />
<br>
<div class="code"><%= link_to "#{@api_domain}channels/#{@channel.id}/feed.json?key=#{@key}", "#{@api_domain}channels/#{@channel.id}/feed.json?key=#{@key}", :target => '_blank' %></div>
<br />
<br>
</li>
<li>
<div class="helplink">(<a target="_blank" href="http://community.thingspeak.com/documentation/#importer"><%= t(:help_options) %></a>)</div>
<div class="helplink">(<a target="_blank" href="/docs/channels#importer"><%= t(:help_options) %></a>)</div>
<h2><%= t(:help) %></h2>
<%= t(:help_channel_import) %>
</li>

View File

@ -18,7 +18,7 @@
<%= f.error_messages %>
<input name='userlogin' class='userlogin' />
<%= f.text_area :body, :rows => 7, :cols => 54 %>
<br />
<br>
<%= submit_tag %>
<% end %>
</div>
@ -56,7 +56,7 @@ function commentflag(m_id, user_id)
type: 'POST',
data: {user_id : user_id},
success: function(data) {
$('#commentflag' + m_id).parent().parent().append("<span id='votemsg" + m_id + "'><br /><br /><%= t(:comment_reported) %></span>");
$('#commentflag' + m_id).parent().parent().append("<span id='votemsg" + m_id + "'><br><br><%= t(:comment_reported) %></span>");
$('#imgflag' + m_id).attr('src', '/images/flag_red.gif');
$('#flaglink' + m_id).removeAttr('onclick');
$('#flaglink' + m_id).unbind('mouseenter mouseleave');

View File

@ -32,7 +32,7 @@
</table>
<br />
<br>
<% end %>
@ -41,18 +41,18 @@
<%= d.submit t(:channel_create), :class => 'btn btn-primary' %>
<% end %>
<% if is_admin? %>
<% if current_admin_user.present? %>
<br /><br /><br />
<br><br><br>
<% @channels.each do |c| %>
<% if c.social %>
<%= t(:social_channel) %>: <%= link_to c.name, (Rails.env == 'production') ? "http://www.socialsensornetwork.com/#{c.slug}" : "/s/#{c.slug}" %>
<br />
<br>
<% end %>
<% end %>
<br />
<br>
<%= link_to t(:social_channel_create), :controller => 'channels', :action => 'social_new' %>
<% end %>
@ -66,7 +66,7 @@
<div class="col-pad">
<%= t(:help_channel) %>
(<a href="http://community.thingspeak.com/documentation/api/"><%= t(:help_options) %></a>)
(<a href="/docs/channels"><%= t(:help_options) %></a>)
<ul>
<li><a href="http://community.thingspeak.com/tutorials/arduino/using-an-arduino-ethernet-shield-to-update-a-thingspeak-channel/">Arduino Tutorial</a></li>
<li><a href="http://community.thingspeak.com/tutorials/netduino/create-your-own-web-of-things-using-the-netduino-plus-and-thingspeak/">Netduino Plus Tutorial</a></li>

View File

@ -1,11 +1,55 @@
<div class="row">
<div class="col-xs-12 col-sm-12">
<h4 class="breadcrumb"><%= flash[:notice] %></h4>
<div class="col-xs-12 col-sm-9">
<h4 class="breadcrumb"><%= @header %></h4>
<%= render :partial => 'list' %>
<br />
<br>
<%= will_paginate @channels %>
</div>
<div class="col-xs-12 col-sm-3">
<h4 class="breadcrumb"><%= t(:search).capitalize %></h4>
<div class="col-pad">
<form role="form">
<div class="form-group">
<label for="tag_input"><%= t(:search_by_tag) %></label>
<input type="text" class="form-control" id="tag_input" placeholder="<%= t(:search_input_tag) %>">
</div>
<button type="submit" id="search_tag" class="btn btn-primary btn-sm"><%= t(:submit) %></button>
</form>
<br><br>
<form role="form">
<div class="form-group">
<label for="username_input"><%= t(:search_by_username) %></label>
<input type="text" class="form-control" id="username_input" placeholder="<%= t(:search_input_username) %>">
</div>
<button type="submit" id="search_username" class="btn btn-primary btn-sm"><%= t(:submit) %></button>
</form>
</div>
</div>
</div>
<script>
// when the document is ready
$(document).ready(function() {
// when search tag is clicked
$('#search_tag').on('click', function() {
window.location.href = '<%= @domain %>channels/public?tag=' + encodeURIComponent($('#tag_input').val());
return false;
});
// when search username is clicked
$('#search_username').on('click', function() {
window.location.href = '<%= @domain %>channels/public?username=' + encodeURIComponent($('#username_input').val());
return false;
});
});
</script>

View File

@ -76,9 +76,9 @@
<li>
<h2><%= t(:help) %></h2>
<%= t(:help_social_channel) %>
<br /><br />
<br><br>
<div class="code">http://www.socialsensornetwork.com/<b>slug</b></div>
<br />
<br>
<%= t(:help_social_channel_public) %>
</li>
</ul>

View File

@ -93,7 +93,7 @@
<% if !@channel.description.blank? %>
<div class="default_text"><%= t(:channel_directions) %>: <%= @channel.description %></div>
<br />
<br>
<% end %>
<%= form_tag "#{@post_url}" do %>
@ -122,19 +122,19 @@
</table>
<% end %>
<br /><br />
<br><br>
<div style="float: left; width: 600px;">
<iframe width="600" height="300" frameborder="0" scrolling="no" style="border: 1px solid #cccccc;" src="<%= @api_domain %>channels/<%= @channel.id %>/maps/default?width=600&height=300&results=100&round=2&status=true&dynamic=true"></iframe>
<% @fields.each do |f| %>
<br /><br />
<br><br>
<iframe width="600" height="300" frameborder="0" style="border: 1px solid #cccccc;" src="<%= @api_domain %>channels/<%= @channel.id %>/charts/<%= f[-1] %>?width=600&height=300&results=100&round=2&color=C46353&location=true&dynamic=true&push=false&max=20000&min=-20000"></iframe>
<% end %>
<div id="footer">
<br /><br />
<br><br>
&copy;2011 Social Sensor Network - Powered by <a href="https://www.thingspeak.com/">ThingSpeak</a>
</div>

View File

@ -1,17 +1,15 @@
<div class="row">
<div class="col-xs-12 col-sm-6">
<div class="col-xs-12 col-sm-12">
<h4 class="breadcrumb"><%= t(:watched_channels) %></h4>
<div class="col-pad">
<% if @channels.empty? %>
<%= t(:watched_empty) %>
<br /><br />
<%= link_to t(:watched_find), public_channels_path %>
<% else %>
<%= render :partial => 'list' %>
<% end %>
</div>
<% if @channels.empty? %>
<%= t(:watched_empty) %>
<br><br>
<%= link_to t(:watched_find), public_channels_path %>
<% else %>
<%= render :partial => 'list' %>
<% end %>
</div>
</div>

View File

@ -2,10 +2,10 @@
<iframe id="iframe<%= index %>" width="<%= width %>" height="<%= height %>" style="border: 1px solid #cccccc;" src="" default_src="<%= src %>"></iframe>
<br /><br />
<br><br>
<%= t(:chart_embed_code) %>:
<br />
<br>
<textarea id="embed<%= index %>" rows="5" cols="53">&lt;iframe width="<%= width %>" height="<%= height %>" style="border: 1px solid #cccccc;" src="<%= src %>">&lt;/iframe></textarea>
<br /><br /><br />
<br><br><br>

View File

@ -23,7 +23,7 @@
<% @channel.attribute_names.each do |attr| %>
<% if attr.index('field') and @channel[attr] and !@channel[attr].empty? %>
<%= render :partial => 'config',
:locals => {
:displayconfig => true,
@ -35,7 +35,7 @@
:height => @height
}
%>
<% end %>
<% end %>
</div>
@ -43,12 +43,12 @@
<div id="sidebar">
<ul>
<li>
<div class="helplink">(<a href="http://community.thingspeak.com/documentation/api/#charts"><%= t(:help_options) %></a>)</div>
<div class="helplink">(<a href="/docs/charts"><%= t(:help_options) %></a>)</div>
<h2><%= t(:help_charts) %></h2>
<%= t(:help_charts_options) %>
<br /><br />
<br><br>
<%= t(:help_charts_embed) %>
<br /><br />
<br><br>
&bull; <a href="http://community.thingspeak.com/tutorials/wordpress/how-to-embed-a-thingspeak-chart-on-your-wordpress-blog/">WordPress Tutorial</a>
</li>
</ul>
@ -64,3 +64,4 @@
});
</script>

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6/jquery.min.js"></script>
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<%= javascript_include_tag 'application' %>
<script type="text/javascript">
@ -11,10 +11,10 @@
// converts date format from JSON
function getChartDate(d) {
// get the data using javascript's date object (year, month, day, hour, minute, second)
// months in javascript start at 0, so remember to subtract 1 when specifying the month
// offset in minutes is converted to milliseconds and subtracted so that chart's x-axis is correct
return Date.UTC(d.substring(0,4), d.substring(5,7)-1, d.substring(8,10), d.substring(11,13), d.substring(14,16), d.substring(17,19)) - (myOffset * 60000);
// get the data using javascript's date object (year, month, day, hour, minute, second)
// months in javascript start at 0, so remember to subtract 1 when specifying the month
// offset in minutes is converted to milliseconds and subtracted so that chart's x-axis is correct
return Date.UTC(d.substring(0,4), d.substring(5,7)-1, d.substring(8,10), d.substring(11,13), d.substring(14,16), d.substring(17,19)) - (myOffset * 60000);
}
$(document).ready(function() {
@ -26,61 +26,51 @@
var last_date;
// get the data with a webservice call
$.getJSON('<%= "#{@domain}channels/#{params[:channel_id]}/field/#{params[:id]}.json?callback=?&offset=0#{@qs}" %>', function(data) {
// if no access
if (data == '-1') {
$('#chart-container').append('<%= t(:chart_no_access) %>');
}
if (data == '-1') { $('#chart-container').append('<%= t(:chart_no_access) %>'); }
// iterate through each feed
$.each(data.feeds, function() {
var p = new Highcharts.Point();
// set the proper values
var v = this.field<%= params[:id] %>;
p.x = getChartDate(this.created_at);
p.y = parseFloat(v);
// add location if possible
if (this.location) { p.name = this.location; }
// if a numerical value exists add it
if (!isNaN(parseInt(v))<% if params[:max] %> && p.y <= <%= params[:max]%><% end %><% if params[:min] %> && p.y >= <%= params[:min]%><% end %>) { chartData.push(p); }
});
var p = new Highcharts.Point();
// set the proper values
var v = this.field<%= params[:id] %>;
p.x = getChartDate(this.created_at);
p.y = parseFloat(v);
// add location if possible
if (this.location) { p.name = this.location; }
// if a numerical value exists add it
if (!isNaN(parseInt(v))<% if params[:max] %> && p.y <= <%= params[:max]%><% end %><% if params[:min] %> && p.y >= <%= params[:min]%><% end %>) { chartData.push(p); }
});
// specify the chart options
var chartOptions = {
chart: {
chart: {
renderTo: 'chart-container',
defaultSeriesType: '<%= params[:type] ? "#{params[:type]}" : "line" %>',
backgroundColor: '<%= params[:bgcolor] || "#ffffff" %>',
events: {
load: function() {
//if dynamic and no "timeslice" options are set
// GAK 02/16/2013 Let's try to add the last "average" slice if params[:average]
renderTo: 'chart-container',
defaultSeriesType: '<%= params[:type] ? "#{params[:type]}" : "line" %>',
backgroundColor: '<%= params[:bgcolor] || "#ffffff" %>',
events: {
load: function() {
//if dynamic and no "timeslice" options are set
// GAK 02/16/2013 Let's try to add the last "average" slice if params[:average]
var url = '<%= "#{@domain}channels/#{params[:channel_id]}/feed/last.json?callback=?&offset=0&location=true#{@qs}" %>' ;
if ("<%= params[:average] %>".length > 0) {
url = '<%= "#{@domain}channels/#{params[:channel_id]}/feed/last_average.json?callback=?&offset=0&location=true&average=#{params[:average]}#{@qs}" %>' ;
} else if ("<%= params[:median] %>".length > 0) {
url = '<%= "#{@domain}channels/#{params[:channel_id]}/feed/last_median.json?callback=?&offset=0&location=true&median=#{params[:median]}#{@qs}" %>' ;
} else if ("<%= params[:sum] %>".length > 0) {
url = '<%= "#{@domain}channels/#{params[:channel_id]}/feed/last_sum.json?callback=?&offset=0&location=true&sum=#{params[:sum]}#{@qs}" %>' ;
}
var url = '<%= "#{@domain}channels/#{params[:channel_id]}/feed/last.json?callback=?&offset=0&location=true#{@qs}" %>' ;
if ("<%= params[:average] %>".length > 0) {
url = '<%= "#{@domain}channels/#{params[:channel_id]}/feed/last_average.json?callback=?&offset=0&location=true&average=#{params[:average]}#{@qs}" %>' ;
}
else if ("<%= params[:median] %>".length > 0) {
url = '<%= "#{@domain}channels/#{params[:channel_id]}/feed/last_median.json?callback=?&offset=0&location=true&median=#{params[:median]}#{@qs}" %>' ;
}
else if ("<%= params[:sum] %>".length > 0) {
url = '<%= "#{@domain}channels/#{params[:channel_id]}/feed/last_sum.json?callback=?&offset=0&location=true&sum=#{params[:sum]}#{@qs}" %>' ;
}
if ('true' === '<%= params[:dynamic] %>' && (
'<%= params[:timescale] %>'.length < 1
)) {
// push data every 15 seconds
setInterval(function() {
// get the data with a webservice call if we're just getting the last channel
$.getJSON(url, function(data) {
// if data exists
if (data && data.field<%= params[:id] %>) {
if ('true' === '<%= params[:dynamic] %>' && ('<%= params[:timescale] %>'.length < 1)) {
// push data every 15 seconds
setInterval(function() {
// get the data with a webservice call if we're just getting the last channel
$.getJSON(url, function(data) {
// if data exists
if (data && data.field<%= params[:id] %>) {
var p = new Highcharts.Point();
// set the proper values
@ -92,7 +82,7 @@
if (data.location) { p.name = data.location; }
// get the last date if possible
if (dynamicChart.series[0].data.length > 0) {
last_date = dynamicChart.series[0].data[dynamicChart.series[0].data.length-1].x;
last_date = dynamicChart.series[0].data[dynamicChart.series[0].data.length-1].x;
}
var shift = <%= (@push=='true') ? 'true' : 'false' %> ; //default for shift
@ -101,19 +91,17 @@
var results = <%= (@results) ? @results : 60 %>;
if ( results && dynamicChart.series[0].data.length+1 >= results ) {
shift = true ;
shift = true ;
}
// if a numerical value exists and it is a new date, add it
if (!isNaN(parseInt(v)) && (p.x != last_date)<% if params[:max] %> && p.y <= <%= params[:max]%><% end %><% if params[:min] %> && p.y >= <%= params[:min]%><% end %>) {
dynamicChart.series[0].addPoint(p, true, shift);
dynamicChart.series[0].addPoint(p, true, shift);
}
else {
dynamicChart.series[0].data[dynamicChart.series[0].data.length-1].update(p);
dynamicChart.series[0].data[dynamicChart.series[0].data.length-1].update(p);
}
}
});
});
}, 15000);
}
@ -169,9 +157,9 @@
enabled: false
},
series: [{
name: data.channel.field<%= params[:id] %>
}]
};
name: data.channel.field<%= params[:id] %>
}]
};
// add the data to the chart
chartOptions.series[0].data = chartData;
@ -184,14 +172,16 @@
// draw the chart
var dynamicChart = new Highcharts.Chart(chartOptions);
});
}); // end getJSON ajax call
});
}); // end document.ready
</script>
</head>
<body style='background-color: <%= params[:bgcolor] ? params[:bgcolor] : 'white' %>;'>
<div id="chart-container" style="width: <%= params[:width] ? params[:width].to_i - 25 : @width.to_i - 25 %>px; height: <%= params[:height] ? params[:height].to_i - 25 : @height.to_i - 25 %>px;"></div>
<div id="chart-container" style="width: <%= params[:width] ? params[:width].to_i - 25 : @width.to_i - 25 %>px; height: <%= params[:height] ? params[:height].to_i - 25 : @height.to_i - 25 %>px; display: table-cell; vertical-align: middle;">
<%= image_tag 'ajax-loader.gif', :style => "margin: auto; display: block;" %>
</div>
</body>
</html>

View File

@ -13,7 +13,7 @@
<% end %>
<span class="username"><%= link_to User.find(comment.user_id).login, list_channels_path(User.find(comment.user_id).login) %></span>
<span class="prettydate"><%= time_ago_in_words(comment.created_at) %> <%= t(:ago) %></span>
<br />
<br>
<div>
<%= auto_link_urls(comment.body) %>
</div>
@ -32,7 +32,7 @@
<input name='userlogin' class='userlogin' />
<%= hidden_field_tag :parent_id, comment.id %>
<%= f.text_area :body, :value => '', :class => 'commentarea' %>
<br />
<br>
<%= submit_tag %>
<% end %>
</div>

View File

@ -0,0 +1,13 @@
<h2>Resend confirmation instructions</h2>
<%= form_for(resource, :as => resource_name, :url => confirmation_path(resource_name), :html => { :method => :post }) do |f| %>
<%= devise_error_messages! %>
<div><%= f.label :login %><br>
<%= f.text_field :login, :autofocus => true %></div>
<div><%= f.submit "Resend confirmation instructions" %></div>
<% end %>
<%= render "devise/shared/links" %>

View File

@ -0,0 +1,5 @@
<p>Welcome <%= @email %>!</p>
<p>You can confirm your account email through the link below:</p>
<p><%= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @token) %></p>

View File

@ -0,0 +1,8 @@
<p>Hello <%= @resource.email %>!</p>
<p>Someone has requested a link to change your password. You can do this through the link below.</p>
<p><%= link_to 'Change my password', edit_password_url(@resource, :reset_password_token => @token) %></p>
<p>If you didn't request this, please ignore this email.</p>
<p>Your password won't change until you access the link above and create a new one.</p>

View File

@ -0,0 +1,7 @@
<p>Hello <%= @resource.email %>!</p>
<p>Your account has been locked due to an excessive number of unsuccessful sign in attempts.</p>
<p>Click the link below to unlock your account:</p>
<p><%= link_to 'Unlock my account', unlock_url(@resource, :unlock_token => @token) %></p>

View File

@ -0,0 +1,28 @@
<div class="row">
<div class="col-sm-6 col-xs-12">
<h4 class="breadcrumb"><%= t(:password_new) %></h4>
<%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :put, :class => 'form-horizontal' }) do |f| %>
<%= devise_error_messages! %>
<%= f.hidden_field :reset_password_token %>
<div class="form-group">
<label class="col-sm-4 col-xs-3 control-label"><%= t(:password) %></label>
<div class="col-sm-8 col-xs-6"><%= f.password_field :password, :autofocus => true, :class => 'form-control' %></div>
</div>
<div class="form-group">
<label class="col-sm-4 col-xs-3 control-label"><%= t(:password_confirmation) %></label>
<div class="col-sm-8 col-xs-6"><%= f.password_field :password_confirmation, :class => 'form-control' %></div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label"></label>
<div class="col-sm-8"><p class="form-control-static"><%= f.submit t(:password_change), :class => 'btn btn-primary' %></p></div>
</div>
<% end %>
</div>
</div>

View File

@ -0,0 +1,34 @@
<div class="col-sm-7 col-xs-12">
<h4 class="breadcrumb"><%= t(:password_forgot) %></h4>
<div class="col-pad">
<%= t(:password_forgot_message) %>
<br><br>
<%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :post, :class => 'form-horizontal' }) do |f| %>
<%= devise_error_messages! %>
<div class="form-group">
<label class="col-sm-3 col-xs-3 control-label"><%= t(:email) %></label>
<div class="col-sm-9 col-xs-9"><%= f.text_field :email, :class => 'form-control' %></div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9 col-xs-offset-3 col-xs-9"><p class="form-control-static"><%= f.submit t(:submit), :class => 'btn btn-primary' %></p></div>
</div>
<% end %>
</div>
</div>
<script type="text/javascript">
document.getElementById('user_email').focus();
</script>

View File

@ -0,0 +1,30 @@
<h2>Edit <%= resource_name.to_s.humanize %></h2>
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %>
<%= devise_error_messages! %>
<div><%= f.label :login %><br>
<%= f.text_field :login, :autofocus => true %></div>
<% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
<div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
<% end %>
<div><%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br>
<%= f.password_field :password, :autocomplete => "off" %></div>
<div><%= f.label :password_confirmation %><br>
<%= f.password_field :password_confirmation %></div>
<div><%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br>
<%= f.password_field :current_password %></div>
<div><%= f.submit "Update" %></div>
<% end %>
<h3>Cancel my account</h3>
<p>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), :data => { :confirm => "Are you sure?" }, :method => :delete %></p>
<%= link_to "Back", :back %>

View File

@ -0,0 +1,51 @@
<div class="row">
<div class="col-sm-8 col-xs-12">
<h4 class="breadcrumb"><%= t(:signup_header) %></h4>
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => {:class => 'form-horizontal'}) do |f| %>
<%= devise_error_messages! %>
<input name='userlogin' class='userlogin' />
<div class="form-group">
<label class="col-sm-4 col-xs-3 control-label"><%= t(:userid) %></label>
<div class="col-sm-8 col-xs-6"><%= f.text_field :login, :class => 'form-control' %></div>
</div>
<div class="form-group">
<label class="col-sm-4 col-xs-3 control-label"><%= t(:email) %></label>
<div class="col-sm-8 col-xs-6"><%= f.text_field :email, :class => 'form-control' %></div>
</div>
<div class="form-group">
<label class="col-sm-4 col-xs-3 control-label"><%= t(:time_zone) %></label>
<div class="col-sm-8 col-xs-8">
<p class="form-control-static">
<%= time_zone_select 'user', 'time_zone', nil, {:default => 'Eastern Time (US & Canada)'}, {:class => 'form-control'} %>
</p>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 col-xs-3 control-label"><%= t(:password) %></label>
<div class="col-sm-8 col-xs-6"><%= f.password_field :password, :class => 'form-control' %></div>
</div>
<div class="form-group">
<label class="col-sm-4 col-xs-3 control-label"><%= t(:password_confirmation) %></label>
<div class="col-sm-8 col-xs-6"><%= f.password_field :password_confirmation, :class => 'form-control' %></div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label"></label>
<div class="col-sm-8"><p class="form-control-static"><%= f.submit t(:create_account), :id => "user_submit", :class => 'btn btn-primary' %></p></div>
</div>
<% end %>
</div>
</div>
<script type="text/javascript">
document.getElementById('user_login').focus();
</script>

View File

@ -0,0 +1,57 @@
<div class="row">
<div class="col-sm-6 col-xs-12">
<h4 class="breadcrumb">
<% if @failed %>
<%= t(:signin_failure) %>
<% else %>
<%= t(:signin_please) %>
<% end %>
</h4>
<% if @failed %><%= t(:signin_try_again) %><br><br><% end %>
<% if @mail_message.present? %><%= @mail_message %><br><br><% end %>
<%= form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => { :id => 'loginform', :class => 'form-horizontal' }) do |f| %>
<input name='userlogin' class='userlogin' />
<div class="form-group">
<label class="col-sm-4 col-xs-3 control-label"><%= t(:userid) %></label>
<div class="col-sm-8 col-xs-9"><%= f.text_field :login, :value => cookies['user_id'], :class => 'form-control' %></div>
</div>
<div class="form-group">
<label class="col-sm-4 col-xs-3 control-label"><%= t(:password) %></label>
<div class="col-sm-8 col-xs-9">
<%= f.password_field :password, :class => 'form-control' %>
<%= link_to t(:forgot), new_user_password_path, :id => 'forgot_password' %>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-4 col-sm-8 col-xs-offset-3 col-xs-9">
<div class="checkbox">
<label>
<%= f.check_box :remember_me, :checked => true %>
<%= t(:remember_me) %>
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-4 col-sm-8 col-xs-offset-3 col-xs-9"><p class="form-control-static"><%= f.submit t(:signin), :class => 'btn btn-primary' %></p></div>
</div>
<% end %>
</div>
</div>
<script type="text/javascript">
var login = document.getElementById('user_login');
if (login.value.length == 0)
login.focus();
else
document.getElementById('user_password').focus();
</script>

View File

@ -0,0 +1,12 @@
<%- if controller_name != 'sessions' %>
<%= link_to t(:signin), new_session_path(resource_name) %><br>
<% end -%>
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
<%= link_to t(:signup), new_registration_path(resource_name) %><br>
<% end -%>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
<%= link_to t(:password_forgot), new_password_path(resource_name) %><br>
<% end -%>

View File

@ -0,0 +1,12 @@
<h2>Resend unlock instructions</h2>
<%= form_for(resource, :as => resource_name, :url => unlock_path(resource_name), :html => { :method => :post }) do |f| %>
<%= devise_error_messages! %>
<div><%= f.label :email %><br>
<%= f.email_field :email, :autofocus => true %></div>
<div><%= f.submit "Resend unlock instructions" %></div>
<% end %>
<%= render "devise/shared/links" %>

View File

@ -0,0 +1,43 @@
<ul class="nav nav-stacked" id="bootstrap-sidebar">
<li class="<%= 'active' if params[:action] == 'index' %>"><a href="/docs">Getting Started</a></li>
<% if params[:action] == 'channels' %>
<li><a href="#channels">Channels</a></li>
<li class="subitem"><a href="#keywords">Keywords</a></li>
<li class="subitem"><a href="#urls">Base URLs</a></li>
<li class="subitem"><a href="#api_keys">API Keys</a></li>
<li class="subitem"><a href="#rate_limits">Rate Limits / Caching</a></li>
<li class="subitem"><a href="#update">Update Channel Feed</a></li>
<li class="subitem"><a href="#get_feed">Get Channel Feed</a></li>
<li class="subitem"><a href="#get_field">Get Channel Field Feed</a></li>
<li class="subitem"><a href="#get_status">Get Status Updates</a></li>
<li class="subitem"><a href="#list_public">List Public Channels</a></li>
<li class="subitem"><a href="#create">Create a Channel</a></li>
<li class="subitem"><a href="#clear">Clear a Channel</a></li>
<li class="subitem"><a href="#delete">Delete a Channel</a></li>
<li class="subitem"><a href="#importer">Importer</a></li>
<% else %>
<li><a href="/docs/channels">Channels</a></li>
<% end %>
<% if params[:action] == 'charts' %>
<li><a href="#charts">Charts</a></li>
<li class="subitem"><a href="#create">Create Charts</a></li>
<li class="subitem"><a href="#embed">Embed Charts</a></li>
<% else %>
<li><a href="/docs/charts">Charts</a></li>
<% end %>
<% if params[:action] == 'users' %>
<li><a href="#users">Users</a></li>
<li class="subitem"><a href="#get_user">Get User Information</a></li>
<li class="subitem"><a href="#list_user_channels">List User's Channels</a></li>
<% else %>
<li><a href="/docs/users">Users</a></li>
<% end %>
<li class="<%= 'active' if params[:action] == 'plugins' %>"><a href="/docs/plugins">Plugins</a></li>
<li class="<%= 'active' if params[:action] == 'errors' %>"><a href="/docs/errors">Error Codes</a></li>
</ul>

View File

@ -0,0 +1,136 @@
<div class="row">
<div class="col-sm-offset-1 col-sm-3 col-xs-12" id="leftcol">
<%= render 'docs/sidebar' %>
</div>
<div class="col-sm-7 col-xs-12">
<h1 id="channels">Channels</h1>
Channels are where your application stores and retrieves any type of data. Each channel has a Private View and a Public View. The Private View is only accessible by signing into your ThingSpeak.com user account. The Public View is what other viewers will see when they visit your ThingSpeak Channel. You can have different info on each view, customize the view with Plugins, and even disable the Public View. <br><br>
<br><br>
<%= image_tag 'channel_public_view.png', :size => '600x533' %>
<br><br><br>
Channel Views have the following features:
<br><br>
<ul>
<li>Channel Watch</li>
<li>Share via Social Networks</li>
<li>Developer Info</li>
<li>Ability to embed ThingSpeak Plugins</li>
<li>Drag-and-drop Organization</li>
<li>Tags</li>
<li>Comments</li>
</ul>
<br><br>
<h4>Channels API</h4>
To read and write to a ThingSpeak Channel, your application must make requests to the ThingSpeak API using HTTP requests. Each ThingSpeak Channel allows for 8 fields of data (both numeric and alphanumeric formats), location information, and a status update. Each entry is stored with a date and time stamp and is assigned a unique Entry ID (entry_id). After the data is stored, you can retrieve the data by time selection or by Entry ID. In addition to storing and retrieving numeric and alphanumeric data, the ThingSpeak API allows for numeric data processing such as timescaling, averaging, median, summing, and rounding. The channel feeds supports JSON, XML, and CSV formats for integration into applications.
<br><br>
<hr />
<h2 id="keywords">Keywords</h2>
Here are some keywords that are used in the API. An understanding of the terms will make the API documentation easier to understand.
<br><br>
<ul>
<li><strong>Channel </strong>- The name for where data can be inserted or retrieved within the ThingSpeak API,&nbsp;identified&nbsp;by a numerical Channel ID</li>
<li><strong>Channel ID </strong>- Every channel has a unique Channel ID. The Channel ID number is used to identify the channel when your application reads data from the channel</li>
<li><strong>Field </strong>- One of eight specific locations for data inside of a channel, identified by a number between 1 to 8 A field can store numeric data from sensors or&nbsp;alphanumeric&nbsp;strings from serial devices or RFID readers</li>
<li><strong>Status </strong>- A short status message to augment the data stored in a channel</li>
<li><strong>Location </strong>- The latitude, longitude, and elevation&nbsp;of where data is being sent from</li>
<li><strong>Feed </strong>- The collective name for the data stored inside a channel, which may be any combination of field data, status updates, and location info</li>
<li><strong>Write API Key</strong> A 16 digit code that allows an application to write data to a channel</li>
<li><strong>Read API Key</strong> A 16 digit code that allows an application to read the data stored in a channel</li>
</ul>
<br><br>
<hr />
<h2 id="urls">Base URL Addresses and Locations</h2>
Regular URL:
<br><br>
<pre>http://api.thingspeak.com</pre>
<br>
Secure URL:
<br><br>
<pre>https://api.thingspeak.com</pre>
<br>
IP Address:
<br><br>
<pre>http://184.106.153.149</pre>
<br>
Cross-domain XML:
<br><br>
<pre>http://api.thingspeak.com/crossdomain.xml</pre>
<br><br>
<hr />
<h2 id="api_keys">API Keys</h2>
<br><br>
<h4>Private / Public Channels</h4>
By default, your channel is private and requires a Read API Key to access its feed. You can make a channel public which gives other users the ability to use your feed without a Read API Key.
<br><br><br>
<h4>Write API Key</h4>
In order to update a channel, you need to know your Write API Key. If your Write API Key gets compromised you can generate a new key.
<br><br>
Follow these steps to get your Write API Key:
<br><br>
<ul>
<li>Select&nbsp;Channels</li>
<li>Select the&nbsp;Channel&nbsp;to update</li>
<li>Select&nbsp;Manage API Keys</li>
</ul>
<br><br>
<h4>Read API Key</h4>
The Read API Key allows your application to read data from the API. You can generate multiple Read API Keys for different applications.
<br><br>
Follow these steps to get a Read API Key:
<br><br>
<ul>
<li>Select&nbsp;Channels</li>
<li>Select the&nbsp;Channel&nbsp;to update</li>
<li>Select&nbsp;Manage API Keys</li>
<li>Select Generate New Read API Key</li>
</ul>
<br><br><br>
<hr />
<h2 id="rate_limits">Rate Limits</h2>
The open service via ThingSpeak.com has a rate limit of an update per channel every 15 seconds. This limit is so that the service can remain free and give everyone a high-level of service. The API source will also be made available on <a href="https://github.com/iobridge/ThingSpeak">GitHub</a> so that you can run this locally or via a shared web host provider. At that point you will be able to to tweak settings for your application requirements.
<br><br>
<h4>Caching</h4>
Caching is implemented on JSON and XML formats on feeds. Feeds that return more than 100 entries are cached for 5 minutes. This will allow great performance for popular applications. The Last API call and feeds that specify "results=100" or less are not cached, so that you can produce real-time applications.
<br><br><br><br><br><br><br><br><br><br><br><br><br><br>
<hr />
<%= render 'docs/channels/update' %>
<hr />
<%= render 'docs/channels/feed' %>
<hr />
<%= render 'docs/channels/field' %>
<hr />
<%= render 'docs/channels/status' %>
<hr />
<%= render 'docs/channels/public_index' %>
<hr />
<%= render 'docs/channels/create' %>
<hr />
<%= render 'docs/channels/clear' %>
<hr />
<%= render 'docs/channels/destroy' %>
<br><br>
<hr />
<%= render 'docs/channels/importer' %>
<br><br><br><br><br><br><br><br><br><br><br><br>
</div>
</div>

View File

@ -0,0 +1,47 @@
<div>
<%= render 'response' %>
<h2 id="clear">Clear a Channel</h2>
</div>
<br>
To clear all feed data from a Channel, send an HTTP DELETE to <code><%= @ssl_api_domain %>channels/<span class="customcode">CHANNEL_ID</span>/feeds<span class="format format-json">.json</span><span class="format format-xml">.xml</span></code> .
<br><br>
Valid parameters:
<ul>
<li><b>api_key</b> (string) - User's API Key; please note that this is different than a Channel API key, and can be found in <a href="/account">your account details</a>. (required).</li>
</ul>
<br>
Example DELETE:
<pre>
DELETE <span class="str"><%= @ssl_api_domain %>channels/<span class="customcode">4</span>/feeds<span class="format format-json">.json</span><span class="format format-xml">.xml</span></span>
api_key=<span class="customcode"><%= @user_api_key %></span>
</pre>
<br>
<div class="format format-block format-text">
The response will be a webpage with your Channel.
</div>
<div class="format format-block format-json">
The response will be an empty JSON array, for example:
<pre class="prettyprint">
[]
</pre>
</div>
<div class="format format-block format-xml">
The response will be an empty XML array, for example:
<pre class="prettyprint">
&lt;?xml version="1.0" encoding="UTF-8"?>
&lt;nil-classes type="array" />
</pre>
</div>

View File

@ -0,0 +1,101 @@
<div>
<%= render 'response' %>
<h2 id="create">Create a Channel</h2>
</div>
<br>
To create a new Channel, send an HTTP POST to <code><%= @ssl_api_domain %>channels<span class="format format-json">.json</span><span class="format format-xml">.xml</span></code> .
<br><br>
Valid parameters:
<ul>
<li><b>api_key</b> (string) - User's API Key; please note that this is different than a Channel API key, and can be found in <a href="/account">your account details</a>. (required).</li>
<li><b>description</b> (string) - Description of the Channel (optional)</li>
<li><b>elevation</b> (integer) - Elevation in meters (optional)</li>
<li><b>field1</b> (string) - Field1 name (optional)</li>
<li><b>field2</b> (string) - Field2 name (optional)</li>
<li><b>field3</b> (string) - Field3 name (optional)</li>
<li><b>field4</b> (string) - Field4 name (optional)</li>
<li><b>field5</b> (string) - Field5 name (optional)</li>
<li><b>field6</b> (string) - Field6 name (optional)</li>
<li><b>field7</b> (string) - Field7 name (optional)</li>
<li><b>field8</b> (string) - Field8 name (optional)</li>
<li><b>latitude</b> (decimal) - Latitude in degrees (optional)</li>
<li><b>longitude</b> (decimal) - Longitude in degrees (optional)</li>
<li><b>name</b> (string) - Name of the Channel (optional)</li>
<li><b>public_flag</b> (true/false) - Whether the Channel should be public, default false (optional)</li>
<li><b>tags</b> (string) - Comma-separated list of tags (optional)</li>
<li><b>url</b> (string) - Webpage URL for the Channel (optional)</li>
</ul>
<br>
Example POST:
<pre>
POST <span class="str"><%= @ssl_api_domain %>channels<span class="format format-json">.json</span><span class="format format-xml">.xml</span></span>
api_key=<span class="customcode"><%= @user_api_key %></span>
name=<span class="customcode">My New Channel</span>
</pre>
<br>
<div class="format format-block-xl format-text">
The response will be a webpage with your newly created Channel.
</div>
<div class="format format-block-xl format-json">
The response will be a JSON object of the new channel, for example:
<pre class="prettyprint">
{
"id": 4,
"name": "My New Channel",
"description": null,
"latitude": null,
"longitude": null,
"created_at": "2014-03-25T13:12:50-04:00",
"elevation": null,
"last_entry_id": null,
"ranking": 15,
"username": "hans",
"tags": [],
"api_keys":
[
{
"api_key": "XXXXXXXXXXXXXXXX",
"write_flag": true
}
]
}
</pre>
</div>
<div class="format format-block-xl format-xml">
The response will be an XML object of the new channel, for example:
<pre class="prettyprint">
&lt;?xml version="1.0" encoding="UTF-8"?>
&lt;channel>
&lt;id type="integer">4&lt;/id>
&lt;name>My New Channel&lt;/name>
&lt;description nil="true" />
&lt;latitude type="decimal" nil="true" />
&lt;longitude type="decimal" nil="true" />
&lt;created-at type="dateTime">2014-03-25T20:17:44-04:00&lt;/created-at>
&lt;elevation nil="true" />
&lt;last-entry-id type="integer" nil="true" />
&lt;ranking type="integer">15&lt;/ranking>
&lt;username>hans&lt;/username>
&lt;tags type="array" />
&lt;api-keys type="array">
&lt;api-key>
&lt;api-key>XXXXXXXXXXXXXXXX&lt;/api-key>
&lt;write-flag type="boolean">true&lt;/write-flag>
&lt;/api-key>
&lt;/api-keys>
&lt;/channel>
</pre>
</div>

View File

@ -0,0 +1,72 @@
<div>
<%= render 'response' %>
<h2 id="delete">Delete a Channel</h2>
</div>
<br>
To create a new Channel, send an HTTP DELETE to <code><%= @ssl_api_domain %>channels/<span class="customcode">CHANNEL_ID</span><span class="format format-json">.json</span><span class="format format-xml">.xml</span></code> ,
replacing <span class="customcode">CHANNEL_ID</span> with the ID of your Channel.
<br><br>
Valid parameters:
<ul>
<li><b>api_key</b> (string) - User's API Key; please note that this is different than a Channel API key, and can be found in <a href="/account">your account details</a>. (required).</li>
</ul>
<br>
Example DELETE:
<pre>
DELETE <span class="str"><%= @ssl_api_domain %>channels/<span class="customcode">4</span><span class="format format-json">.json</span><span class="format format-xml">.xml</span></span>
api_key=<span class="customcode"><%= @user_api_key %></span>
</pre>
<br>
<div class="format format-block-xl format-text">
The response will be a webpage with a list of Channels.
</div>
<div class="format format-block-xl format-json">
The response will be a JSON object of the Channel before it was deleted, for example:
<pre class="prettyprint">
{
"id": 4,
"name": "My New Channel",
"description": null,
"latitude": null,
"longitude": null,
"created_at": "2014-03-25T13:12:50-04:00",
"elevation": null,
"last_entry_id": null,
"ranking": 15,
"username": "hans",
"tags": []
}
</pre>
</div>
<div class="format format-block-xl format-xml">
The response will be an XML object of the Channel before it was deleted, for example:
<pre class="prettyprint">
&lt;?xml version="1.0" encoding="UTF-8"?>
&lt;channel>
&lt;id type="integer">4&lt;/id>
&lt;name>My New Channel&lt;/name>
&lt;description nil="true" />
&lt;latitude type="decimal" nil="true" />
&lt;longitude type="decimal" nil="true" />
&lt;created-at type="dateTime">2014-03-25T20:17:44-04:00&lt;/created-at>
&lt;elevation nil="true" />
&lt;last-entry-id type="integer" nil="true" />
&lt;ranking type="integer">15&lt;/ranking>
&lt;username>hans&lt;/username>
&lt;tags type="array" />
&lt;/channel>
</pre>
</div>

View File

@ -0,0 +1,305 @@
<div>
<%= render 'response' %>
<h2 id="get_feed">Get a Channel Feed</h2>
</div>
<br>
To view a Channel feed, send an HTTP GET to <code><%= @ssl_api_domain %>channels/<span class="customcode">CHANNEL_ID</span>/feeds<span class="format format-json">.json</span><span class="format format-xml">.xml</span></code> ,
replacing <span class="customcode">CHANNEL_ID</span> with the ID of your Channel.
<br><br>
Valid parameters:
<ul>
<li><b>key</b> (string) Read API Key for this specific Channel (optional--no key required for public channels)</li>
<li><b>results</b> (integer) Number of entries to retrieve, 8000 max, default of 100 (optional)</li>
<li><b>days</b> (integer) Days from now to include in feed (optional)</li>
<li><b>start</b> (datetime) Start date in format YYYY-MM-DD%20HH:NN:SS (optional)</li>
<li><b>end</b> (datetime) End date in format YYYY-MM-DD%20HH:NN:SS (optional)</li>
<li><b>offset</b> (integer) Offset of your timezone without daylight savings time (optional)</li>
<li><b>status</b> (true/false) Include status updates in feed by setting "status=true" (optional)</li>
<li><b>location</b> (true/false) Include latitude, longitude, and elevation in feed by setting "location=true" (optional)</li>
<li><b>min</b> (decimal) Minimum value to include in response (optional)</li>
<li><b>max</b> (decimal) Maximum value to include in response (optional)</li>
<li><b>round</b> (integer) Round to this many decimal places (optional)</li>
<li><b>timescale</b> (integer or string) Get first value in this many minutes, valid values: 10, 15, 20, 30, 60, 240, 720, 1440, "daily" (optional)</li>
<li><b>sum</b> (integer or string) Get sum of this many minutes, valid values: 10, 15, 20, 30, 60, 240, 720, 1440, "daily" (optional)</li>
<li><b>average</b> (integer or string) Get average of this many minutes, valid values: 10, 15, 20, 30, 60, 240, 720, 1440, "daily" (optional)</li>
<li><b>median</b> (integer or string) Get median of this many minutes, valid values: 10, 15, 20, 30, 60, 240, 720, 1440, "daily" (optional)</li>
<li><b>callback</b> (string) Function name to be used for JSONP cross-domain requests (optional)</li>
</ul>
<div class="alert alert-warning">
Please note that the results parameter is not compatible with timescale, sum, average, or median.
</div>
<br>
Example GET:
<pre>GET <span class="str"><%= @ssl_api_domain %>channels/<span class="customcode">9</span>/feeds<span class="format format-json">.json</span><span class="format format-xml">.xml</span>?results=<span class="customcode">2</span></span></pre>
<br>
<div class="format format-block-xxl format-text">
The response will be an HTML page with the JSON Channel feed, for example:
<pre class="prettyprint">
{
"channel":
{
"id": 9,
"name": "my_house",
"description": "Netduino Plus connected to sensors around the house",
"latitude": "40.44",
"longitude": "-79.996",
"field1": "Light",
"field2": "Outside Temperature",
"created_at": "2010-12-13T20:20:06-05:00",
"updated_at": "2014-02-26T12:43:04-05:00",
"last_entry_id": 6060625
},
"feeds":
[
{
"created_at": "2014-02-26T12:42:49-05:00",
"entry_id": 6060624,
"field1": "188",
"field2": "25.902335456475583"
},
{
"created_at": "2014-02-26T12:43:04-05:00",
"entry_id": 6060625,
"field1": "164",
"field2": "25.222929936305732"
}
]
}
</pre>
</div>
<div class="format format-block-xxl format-json">
The response will be a JSON object of the Channel feed, for example:
<pre class="prettyprint">
{
"channel":
{
"id": 9,
"name": "my_house",
"description": "Netduino Plus connected to sensors around the house",
"latitude": "40.44",
"longitude": "-79.996",
"field1": "Light",
"field2": "Outside Temperature",
"created_at": "2010-12-13T20:20:06-05:00",
"updated_at": "2014-02-26T12:43:04-05:00",
"last_entry_id": 6060625
},
"feeds":
[
{
"created_at": "2014-02-26T12:42:49-05:00",
"entry_id": 6060624,
"field1": "188",
"field2": "25.902335456475583"
},
{
"created_at": "2014-02-26T12:43:04-05:00",
"entry_id": 6060625,
"field1": "164",
"field2": "25.222929936305732"
}
]
}
</pre>
</div>
<div class="format format-block-xxl format-xml">
The response will be an XML object of the Channel feed, for example:
<pre class="prettyprint">
&lt;?xml version="1.0" encoding="UTF-8"?>
&lt;channel>
&lt;id type="integer">9&lt;/id>
&lt;name>my_house&lt;/name>
&lt;description>Netduino Plus connected to sensors around the house&lt;/description>
&lt;latitude type="decimal">40.44&lt;/latitude>
&lt;longitude type="decimal">-79.996&lt;/longitude>
&lt;field1>Light&lt;/field1>
&lt;field2>Outside Temperature&lt;/field2>
&lt;created-at type="dateTime">2010-12-13T20:20:06-05:00&lt;/created-at>
&lt;updated-at type="dateTime">2014-02-26T12:49:19-05:00&lt;/updated-at>
&lt;last-entry-id type="integer">6060650&lt;/last-entry-id>
&lt;feeds type="array">
&lt;feed>
&lt;created-at type="dateTime">2014-02-26T12:49:04-05:00&lt;/created-at>
&lt;entry-id type="integer">6060649&lt;/entry-id>
&lt;field1>160&lt;/field1>
&lt;field2>25.307855626326962&lt;/field2>
&lt;id type="integer" nil="true"/>
&lt;/feed>
&lt;feed>
&lt;created-at type="dateTime">2014-02-26T12:49:19-05:00&lt;/created-at>
&lt;entry-id type="integer">6060650&lt;/entry-id>
&lt;field1>171&lt;/field1>
&lt;field2>22.929936305732483&lt;/field2>
&lt;id type="integer" nil="true"/>
&lt;/feed>
&lt;/feeds>
&lt;/channel>
</pre>
</div>
<br>
Live examples:
<ul>
<li><a href="http://api.thingspeak.com/channels/9/feeds.json">http://api.thingspeak.com/channels/9/feeds.json</a></li>
<li><a href="http://api.thingspeak.com/channels/9/feeds.json?median=10">http://api.thingspeak.com/channels/9/feeds.json?median=10</a></li>
<li><a href="http://api.thingspeak.com/channels/9/feeds.json?start=2011-11-11%2010:10:10&amp;end=2011-11-11%2011:11:11">http://api.thingspeak.com/channels/9/feeds.json?start=2011-11-11%2010:10:10&amp;end=2011-11-11%2011:11:11</a></li>
</ul>
<br><br>
<div>
<%= render 'response' %>
<h2>Get Last Entry in a Channel Feed</h2>
</div>
<br>
To get the last entry in a Channel feed, send an HTTP GET to <code><%= @ssl_api_domain %>channels/<span class="customcode">CHANNEL_ID</span>/feeds/last<span class="format format-json">.json</span><span class="format format-xml">.xml</span></code> ,
replacing <span class="customcode">CHANNEL_ID</span> with the ID of your Channel.
<br><br>
Valid parameters:
<ul>
<li><b>key</b> (string) Read API Key for this specific Channel (optional--no key required for public channels)</li>
<li><b>offset</b> (integer) Offset of your timezone without daylight savings time (optional)</li>
<li><b>status</b> (true/false) Include status updates in feed by setting "status=true" (optional)</li>
<li><b>location</b> (true/false) Include latitude, longitude, and elevation in feed by setting "location=true" (optional)</li>
<li><b>callback</b> (string) Function name to be used for JSONP cross-domain requests (optional)</li>
</ul>
<br>
Example GET:
<pre>GET <span class="str"><%= @ssl_api_domain %>channels/<span class="customcode">9</span>/feeds/last<span class="format format-json">.json</span><span class="format format-xml">.xml</span></span></pre>
<br>
<div class="format format-block format-text">
The response will be a JSON object of the most recent feed, for example:
<pre class="prettyprint">
{
"created_at": "2014-02-26T21:27:21Z",
"entry_id": 6061519,
"field1": "176",
"field2": "28.195329087048833"
}
</pre>
</div>
<div class="format format-block format-json">
The response will be a JSON object of the most recent feed, for example:
<pre class="prettyprint">
{
"created_at": "2014-02-26T21:27:21Z",
"entry_id": 6061519,
"field1": "176",
"field2": "28.195329087048833"
}
</pre>
</div>
<div class="format format-block format-xml">
The response will be an XML object of the most recent feed, for example:
<pre class="prettyprint">
&lt;?xml version="1.0" encoding="UTF-8"?>
&lt;feed>
&lt;created-at type="dateTime">2014-02-26T21:28:51Z&lt;/created-at>
&lt;entry-id type="integer">6061525&lt;/entry-id>
&lt;field1>200&lt;/field1>
&lt;field2>28.365180467091296&lt;/field2>
&lt;id type="integer" nil="true"/>
&lt;/feed>
</pre>
</div>
<br><br>
<div>
<%= render 'response' %>
<h2>Get Specific Entry in a Channel</h2>
</div>
<br>
To get a specific entry in a Channel's feed, send an HTTP GET to <code><%= @ssl_api_domain %>channels/<span class="customcode">CHANNEL_ID</span>/feeds/<span class="customcode">ENTRY_ID</span><span class="format format-json">.json</span><span class="format format-xml">.xml</span></code> ,
replacing <span class="customcode">CHANNEL_ID</span> with the ID of your Channel and <span class="customcode">ENTRY_ID</span> with the ID of your entry.
<br><br>
Valid parameters:
<ul>
<li><b>key</b> (string) Read API Key for this specific Channel (optional--no key required for public channels)</li>
<li><b>offset</b> (integer) Offset of your timezone without daylight savings time (optional)</li>
<li><b>status</b> (true/false) Include status updates in feed by setting "status=true" (optional)</li>
<li><b>location</b> (true/false) Include latitude, longitude, and elevation in feed by setting "location=true" (optional)</li>
<li><b>callback</b> (string) Function name to be used for JSONP cross-domain requests (optional)</li>
</ul>
<br>
Example GET:
<pre>GET <span class="str"><%= @ssl_api_domain %>channels/<span class="customcode">9</span>/feeds/<span class="customcode">6061519</span><span class="format format-json">.json</span><span class="format format-xml">.xml</span></span></pre>
<br>
<div class="format format-block format-text">
The response will be a JSON object of the feed entry, for example:
<pre class="prettyprint">
{
"created_at": "2014-02-26T21:27:21Z",
"entry_id": 6061519,
"field1": "176",
"field2": "28.195329087048833"
}
</div>
<div class="format format-block format-json">
The response will be a JSON object of the feed entry, for example:
<pre class="prettyprint">
{
"created_at": "2014-02-26T21:27:21Z",
"entry_id": 6061519,
"field1": "176",
"field2": "28.195329087048833"
}
</pre>
</div>
<div class="format format-block format-xml">
The response will be an XML object of the most recent feed, for example:
<pre class="prettyprint">
&lt;?xml version="1.0" encoding="UTF-8"?>
&lt;feed>
&lt;created-at type="dateTime">2014-02-26T21:27:21Z&lt;/created-at>
&lt;entry-id type="integer">6061519&lt;/entry-id>
&lt;field1>176&lt;/field1>
&lt;field2>28.195329087048833&lt;/field2>
&lt;id type="integer" nil="true"/>
&lt;/feed>
</pre>
</div>

View File

@ -0,0 +1,213 @@
<div>
<%= render 'response' %>
<h2 id="get_field">Get a Channel Field Feed</h2>
</div>
<br>
To view a Channel's field feed, send an HTTP GET to <code><%= @ssl_api_domain %>channels/<span class="customcode">CHANNEL_ID</span>/fields/<span class="customcode">FIELD_ID</span><span class="format format-json">.json</span><span class="format format-xml">.xml</span></code> ,
replacing <span class="customcode">CHANNEL_ID</span> with the ID of your Channel and <span class="customcode">FIELD_ID</span> with the ID of your field.
<br><br>
Valid parameters:
<ul>
<li><b>key</b> (string) Read API Key for this specific Channel (optional--no key required for public channels)</li>
<li><b>results</b> (integer) Number of entries to retrieve, 8000 max, default of 100 (optional)</li>
<li><b>days</b> (integer) Days from now to include in feed (optional)</li>
<li><b>start</b> (datetime) Start date in format YYYY-MM-DD%20HH:NN:SS (optional)</li>
<li><b>end</b> (datetime) End date in format YYYY-MM-DD%20HH:NN:SS (optional)</li>
<li><b>offset</b> (integer) Offset of your timezone without daylight savings time (optional)</li>
<li><b>status</b> (true/false) Include status updates in feed by setting "status=true" (optional)</li>
<li><b>location</b> (true/false) Include latitude, longitude, and elevation in feed by setting "location=true" (optional)</li>
<li><b>min</b> (decimal) Minimum value to include in response (optional)</li>
<li><b>max</b> (decimal) Maximum value to include in response (optional)</li>
<li><b>round</b> (integer) Round to this many decimal places (optional)</li>
<li><b>timescale</b> (integer or string) Get first value in this many minutes, valid values: 10, 15, 20, 30, 60, 240, 720, 1440, "daily" (optional)</li>
<li><b>sum</b> (integer or string) Get sum of this many minutes, valid values: 10, 15, 20, 30, 60, 240, 720, 1440, "daily" (optional)</li>
<li><b>average</b> (integer or string) Get average of this many minutes, valid values: 10, 15, 20, 30, 60, 240, 720, 1440, "daily" (optional)</li>
<li><b>median</b> (integer or string) Get median of this many minutes, valid values: 10, 15, 20, 30, 60, 240, 720, 1440, "daily" (optional)</li>
<li><b>callback</b> (string) Function name to be used for JSONP cross-domain requests (optional)</li>
</ul>
<div class="alert alert-warning">
Please note that the results parameter is not compatible with timescale, sum, average, or median.
</div>
<br>
Example GET:
<pre>GET <span class="str"><%= @ssl_api_domain %>channels/<span class="customcode">9</span>/fields/<span class="customcode">1</span><span class="format format-json">.json</span><span class="format format-xml">.xml</span>?results=<span class="customcode">2</span></span></pre>
<br>
<div class="format format-block-xxl format-text">
The response will be an HTML page with the JSON Channel's field feed, for example:
<pre class="prettyprint">
{
"channel":
{
"id": 9,
"name": "my_house",
"description": "Netduino Plus connected to sensors around the house",
"latitude": "40.44",
"longitude": "-79.996",
"field1": "Light",
"field2": "Outside Temperature",
"created_at": "2010-12-13T20:20:06-05:00",
"updated_at": "2014-02-26T12:43:04-05:00",
"last_entry_id": 6060625
},
"feeds":
[
{
"created_at": "2014-02-26T12:42:49-05:00",
"entry_id": 6060624,
"field1": "188"
},
{
"created_at": "2014-02-26T12:43:04-05:00",
"entry_id": 6060625,
"field1": "164"
}
]
}
</pre>
</div>
<div class="format format-block-xxl format-json">
The response will be a JSON object of the Channel's field feed, for example:
<pre class="prettyprint">
{
"channel":
{
"id": 9,
"name": "my_house",
"description": "Netduino Plus connected to sensors around the house",
"latitude": "40.44",
"longitude": "-79.996",
"field1": "Light",
"field2": "Outside Temperature",
"created_at": "2010-12-13T20:20:06-05:00",
"updated_at": "2014-02-26T12:43:04-05:00",
"last_entry_id": 6060625
},
"feeds":
[
{
"created_at": "2014-02-26T12:42:49-05:00",
"entry_id": 6060624,
"field1": "188"
},
{
"created_at": "2014-02-26T12:43:04-05:00",
"entry_id": 6060625,
"field1": "164"
}
]
}
</pre>
</div>
<div class="format format-block-xxl format-xml">
The response will be an XML object of the Channel's field feed, for example:
<pre class="prettyprint">
&lt;?xml version="1.0" encoding="UTF-8"?>
&lt;channel>
&lt;id type="integer">9&lt;/id>
&lt;name>my_house&lt;/name>
&lt;description>Netduino Plus connected to sensors around the house&lt;/description>
&lt;latitude type="decimal">40.44&lt;/latitude>
&lt;longitude type="decimal">-79.996&lt;/longitude>
&lt;field1>Light&lt;/field1>
&lt;field2>Outside Temperature&lt;/field2>
&lt;created-at type="dateTime">2010-12-13T20:20:06-05:00&lt;/created-at>
&lt;updated-at type="dateTime">2014-02-26T12:49:19-05:00&lt;/updated-at>
&lt;last-entry-id type="integer">6060650&lt;/last-entry-id>
&lt;feeds type="array">
&lt;feed>
&lt;created-at type="dateTime">2014-02-26T12:49:04-05:00&lt;/created-at>
&lt;entry-id type="integer">6060649&lt;/entry-id>
&lt;field1>160&lt;/field1>
&lt;id type="integer" nil="true"/>
&lt;/feed>
&lt;feed>
&lt;created-at type="dateTime">2014-02-26T12:49:19-05:00&lt;/created-at>
&lt;entry-id type="integer">6060650&lt;/entry-id>
&lt;field1>171&lt;/field1>
&lt;id type="integer" nil="true"/>
&lt;/feed>
&lt;/feeds>
&lt;/channel>
</pre>
</div>
<br><br>
<div>
<%= render 'response' %>
<h2>Get Last Entry in a Field Feed</h2>
</div>
<br>
To get the last entry in a Channel's field feed, send an HTTP GET to <code><%= @ssl_api_domain %>channels/<span class="customcode">CHANNEL_ID</span>/fields/<span class="customcode">FIELD_ID</span>/last<span class="format format-json">.json</span><span class="format format-xml">.xml</span></code> ,
replacing <span class="customcode">CHANNEL_ID</span> with the ID of your Channel and <span class="customcode">FIELD_ID</span> with the ID of your field.
<br><br>
Valid parameters:
<ul class="format-block">
<li><b>key</b> (string) Read API Key for this specific Channel (optional--no key required for public channels)</li>
<li><b>offset</b> (integer) Offset of your timezone without daylight savings time (optional)</li>
<li><b>status</b> (true/false) Include status updates in feed by setting "status=true" (optional)</li>
<li><b>location</b> (true/false) Include latitude, longitude, and elevation in feed by setting "location=true" (optional)</li>
<li><b>callback</b> (string) Function name to be used for JSONP cross-domain requests (optional)</li>
<li class="format format-text"><b>prepend</b> (string) Text to add before the API response (optional)</li>
<li class="format format-text"><b>append</b> (string) Text to add after the API response (optional)</li>
</ul>
<br>
Example GET:
<pre>GET <span class="str"><%= @ssl_api_domain %>channels/<span class="customcode">9</span>/fields/<span class="customcode">1</span>/last<span class="format format-json">.json</span><span class="format format-xml">.xml</span></span></pre>
<br>
<div class="format format-block format-text">
The response will be the Channel field's most recent value, for example:
<pre class="prettyprint">
176
</pre>
</div>
<div class="format format-block format-json">
The response will be a JSON object of the most recent feed, for example:
<pre class="prettyprint">
{
"created_at": "2014-02-26T21:27:21Z",
"entry_id": 6061519,
"field1": "176"
}
</pre>
</div>
<div class="format format-block format-xml">
The response will be an XML object of the most recent feed, for example:
<pre class="prettyprint">
&lt;?xml version="1.0" encoding="UTF-8"?>
&lt;feed>
&lt;created-at type="dateTime">2014-02-26T21:28:51Z&lt;/created-at>
&lt;entry-id type="integer">6061525&lt;/entry-id>
&lt;field1>200&lt;/field1>
&lt;id type="integer" nil="true"/>
&lt;/feed>
</pre>
</div>

View File

@ -0,0 +1,8 @@
<h2 id="importer">Importer</h2>
<p>Using the ThingSpeak Importer, you are able to import data from a CSV file directly into a ThingSpeak Channel. The access the Importer, select a <em>Channel</em>, and click <em>Import Data</em>.</p>
<p>The format for the CSV should be the following:</p>
<pre>datetime,field1,field2,field3,field4,field5,field6,field7,field8,latitude,longitude,elevation,status</pre>
<p>Here is an example CSV file: <a title="ThingSpeak Import Sample Data" href="/files/importer_sample.csv" target="_blank">Sample CSV File</a></p>
You only have to send a datetime stamp and at least one field. The datetime stamp can be in many formats such as epoch, ISO 8601, or MySQL time. If the datetime includes a GMT/UTC offset, we will use that to properly import the data. If your datetime stamps do not have a GMT / UTC offset, you can specify a time zone that the data was logged in.

View File

@ -0,0 +1,149 @@
<div>
<%= render 'response' %>
<h2 id="list_public">List Public Channels</h2>
</div>
<br>
To view a list of public Channels, send an HTTP GET to
<br>
<code><%= @ssl_api_domain %>channels/public<span class="format format-json">.json</span><span class="format format-xml">.xml</span></code> .
<br><br>
Valid parameters:
<ul>
<li><b>page</b> (integer) Page number to retrieve (optional)</li>
<li><b>tag</b> (string) Name of tag to search for (optional)</li>
<li><b>username</b> (string) Person's username that you want to search Channels for (optional)</li>
</ul>
<br>
Example GET:
<pre>GET <span class="str"><%= @ssl_api_domain %>channels/public<span class="format format-json">.json</span><span class="format format-xml">.xml</span></span></pre>
<br>
<div class="format format-block-xxxl format-text">
The response will be a webpage with a <a href="/channels/public">list of public Channels</a>.
</div>
<div class="format format-block-xxxl format-json">
The response will be a JSON object of public Channels, for example:
<pre class="prettyprint">
{
"pagination":
{
"current_page": 1,
"per_page": 15,
"total_entries": 653
},
"channels":
[
{
"id": 9,
"name": "my_house",
"description": "Netduino Plus connected to sensors around the house",
"latitude": "40.44",
"longitude": "-79.996",
"created_at": "2010-12-13T20:20:06-05:00",
"elevation": "",
"last_entry_id": 6062691,
"ranking" :100,
"username":"hans",
"tags":
[
{
"id": 9,
"name": "temp"
},{
"id": 25,
"name": "light"
}
]
},
{
"id": 5683,
"name": "Residential Data Points",
"description": "Arduino Uno + Ethernet Shield",
"latitude": "35.664548",
"longitude": "-78.654972",
"created_at": "2013-05-15T12:33:57-04:00",
"elevation": "100",
"last_entry_id": 731713,
"ranking": 100,
"username": "samlro",
"tags":
[
{
"id": 950,
"name": "Analog Inputs"
}
]
}
]
}
</pre>
</div>
<div class="format format-block-xxxl format-xml">
The response will be an XML object of public Channels, for example:
<pre class="prettyprint">
&lt;?xml version="1.0" encoding="UTF-8"?>
&lt;response>
&lt;pagination>
&lt;current-page type="WillPaginate::PageNumber">1&lt;/current-page>
&lt;per-page type="integer">15&lt;/per-page>
&lt;total-entries type="integer">654&lt;/total-entries>
&lt;/pagination>
&lt;channels type="array">
&lt;channel>
&lt;id type="integer">9&lt;/id>
&lt;name>my_house&lt;/name>
&lt;description>
Netduino Plus connected to sensors around the house
&lt;/description>
&lt;latitude type="decimal">40.44&lt;/latitude>
&lt;longitude type="decimal">-79.996&lt;/longitude>
&lt;created-at type="dateTime">2010-12-13T20:20:06-05:00&lt;/created-at>
&lt;elevation/>
&lt;last-entry-id type="integer">6062720&lt;/last-entry-id>
&lt;ranking type="integer">100&lt;/ranking>
&lt;username>hans&lt;/username>
&lt;tags type="array">
&lt;tag>
&lt;id type="integer">9&lt;/id>
&lt;name>temp&lt;/name>
&lt;/tag>
&lt;tag>
&lt;id type="integer">25&lt;/id>
&lt;name>light&lt;/name>
&lt;/tag>
&lt;/tags>
&lt;/channel>
&lt;channel>
&lt;id type="integer">5683&lt;/id>
&lt;name>Residential Data Points&lt;/name>
&lt;description>Arduino Uno + Ethernet Shield&lt;/description>
&lt;latitude type="decimal">35.664548&lt;/latitude>
&lt;longitude type="decimal">-78.654972&lt;/longitude>
&lt;created-at type="dateTime">2013-05-15T12:33:57-04:00&lt;/created-at>
&lt;elevation>100&lt;/elevation>
&lt;last-entry-id type="integer">731720&lt;/last-entry-id>
&lt;ranking type="integer">100&lt;/ranking>
&lt;username>samlro&lt;/username>
&lt;tags type="array">
&lt;tag>
&lt;id type="integer">950&lt;/id>
&lt;name>Analog Inputs&lt;/name>
&lt;/tag>
&lt;/tags>
&lt;/channel>
&lt;/channels>
&lt;/response>
</pre>
</div>

View File

@ -0,0 +1,109 @@
<div>
<%= render 'response' %>
<h2 id="get_status">Get Status Updates</h2>
</div>
<br>
To view a Channel's status updates, send an HTTP GET to <code><%= @ssl_api_domain %>channels/<span class="customcode">CHANNEL_ID</span>/status<span class="format format-json">.json</span><span class="format format-xml">.xml</span></code> ,
replacing <span class="customcode">CHANNEL_ID</span> with the ID of your Channel.
<br><br>
Valid parameters:
<ul>
<li><b>key</b> (string) Read API Key for this specific Channel (optional--no key required for public channels)</li>
<li><b>offset</b> (integer) Offset of your timezone without daylight savings time (optional)</li>
<li><b>callback</b> (string) Function name to be used for JSONP cross-domain requests (optional)</li>
</ul>
<br>
Example GET:
<pre>GET <span class="str"><%= @ssl_api_domain %>channels/<span class="customcode">1417</span>/status<span class="format format-json">.json</span><span class="format format-xml">.xml</span></span></pre>
<br>
<div class="format format-block-xxl format-text">
The response will be a JSON object of Channel statuses, for example:
<pre class="prettyprint">
{
"channel":
{
"name": "CheerLights",
"latitude": "40.5",
"longitude": "-80.22"
},
"feeds":
[
{
"created_at": "2014-02-26T02:28:01Z",
"entry_id": 11888,
"status": "@cheerlights green"
},
{
"created_at": "2014-02-26T22:05:31Z",
"entry_id" :11889,
"status": "@cheerlights blue"
}
]
}
</pre>
</div>
<div class="format format-block-xxl format-json">
The response will be a JSON object of Channel statuses, for example:
<pre class="prettyprint">
{
"channel":
{
"name": "CheerLights",
"latitude": "40.5",
"longitude": "-80.22"
},
"feeds":
[
{
"created_at": "2014-02-26T02:28:01Z",
"entry_id": 11888,
"status": "@cheerlights green"
},
{
"created_at": "2014-02-26T22:05:31Z",
"entry_id" :11889,
"status": "@cheerlights blue"
}
]
}
</pre>
</div>
<div class="format format-block-xxl format-xml">
The response will be an XML object of Channel statuses, for example:
<pre class="prettyprint">
&lt;?xml version="1.0" encoding="UTF-8"?>
&lt;channel>
&lt;name>CheerLights&lt;/name>
&lt;latitude type="decimal">40.5&lt;/latitude>
&lt;longitude type="decimal">-80.22&lt;/longitude>
&lt;feeds type="array">
&lt;feed>
&lt;created-at type="dateTime">2014-02-26T02:28:01Z&lt;/created-at>
&lt;entry-id type="integer">11888&lt;/entry-id>
&lt;status>@cheerlights green&lt;/status>
&lt;id type="integer" nil="true"/>
&lt;/feed>
&lt;feed>
&lt;created-at type="dateTime">2014-02-26T22:05:31Z&lt;/created-at>
&lt;entry-id type="integer">11889&lt;/entry-id>
&lt;status>@cheerlights blue&lt;/status>
&lt;id type="integer" nil="true"/>
&lt;/feed>
&lt;/feeds>
&lt;/channel>
</pre>
</div>

View File

@ -0,0 +1,100 @@
<div>
<%= render 'response' %>
<h2 id="update">Update Channel Feed</h2>
</div>
<br>
To update a Channel feed, send an HTTP GET or POST to<br>
<code><%= @ssl_api_domain %>update<span class="format format-json">.json</span><span class="format format-xml">.xml</span></code> .
<br><br>
Valid parameters:
<ul>
<li><b>api_key</b> (string) - Write API Key for this specific Channel (required). The Write API Key can optionally be sent via an X-THINGSPEAKAPIKEY HTTP header.</li>
<li><b>field1</b> (string) - Field 1 data (optional)</li>
<li><b>field2</b> (string) - Field 2 data (optional)</li>
<li><b>field3</b> (string) - Field 3 data (optional)</li>
<li><b>field4</b> (string) - Field 4 data (optional)</li>
<li><b>field5</b> (string) - Field 5 data (optional)</li>
<li><b>field6</b> (string) - Field 6 data (optional)</li>
<li><b>field7</b> (string) - Field 7 data (optional)</li>
<li><b>field8</b> (string) - Field 8 data (optional)</li>
<li><b>lat</b> (decimal) - Latitude in degrees (optional)</li>
<li><b>long</b> (decimal) - Longitude in degrees (optional)</li>
<li><b>elevation</b> (integer) - Elevation in meters (optional)</li>
<li><b>status</b> (string) - Status update message (optional)</li>
<li><b>twitter</b> (string) - Twitter username linked to <a href="/docs/thingtweet">ThingTweet</a> (optional)</li>
<li><b>tweet</b> (string) - Twitter status update; see <a href="/docs/thingtweet#update">updating ThingTweet</a> for more info (optional)</li>
<li><b>created_at</b> (datetime) - Date when this feed entry was created, in the format<br>YYYY-MM-DD%20HH:NN:SS (optional)</li>
</ul>
<br>
Example POST:
<pre>
POST <span class="str"><%= @ssl_api_domain %>update<span class="format format-json">.json</span><span class="format format-xml">.xml</span></span>
api_key=<span class="customcode"><%= @channel_api_key %></span>
field1=<span class="customcode">73</span>
</pre>
<br>
<div class="format format-block-xl format-text">
The response will be the entry ID of the update, for example: <code>18</code>
<br><br>
If the response is <code>0</code> then the update failed.
</div>
<div class="format format-block-xl format-json">
The response will be a JSON object of the new feed, for example:
<pre class="prettyprint">
{
"channel_id": 3,
"field1": "73",
"field2": null,
"field3": null,
"field4": null,
"field5": null,
"field6": null,
"field7": null,
"field8": null,
"created_at": "2014-02-25T14:13:01-05:00",
"entry_id": 320,
"status": null,
"latitude": null,
"longitude": null,
"elevation": null,
"location":null
}
</pre>
</div>
<div class="format format-block-xl format-xml">
The response will be an XML object of the new feed, for example:
<pre class="prettyprint">
&lt;?xml version="1.0" encoding="UTF-8"?>
&lt;feed>
&lt;channel-id type="integer">3&lt;/channel-id>
&lt;field1>73&lt;/field1>
&lt;field2 nil="true"/>
&lt;field3 nil="true"/>
&lt;field4 nil="true"/>
&lt;field5 nil="true"/>
&lt;field6 nil="true"/>
&lt;field7 nil="true"/>
&lt;field8 nil="true"/>
&lt;created-at type="dateTime">2014-02-25T14:15:42-05:00&lt;/created-at>
&lt;entry-id type="integer">321&lt;/entry-id>
&lt;status nil="true"/>
&lt;latitude type="decimal" nil="true"/>
&lt;longitude type="decimal" nil="true"/>
&lt;elevation nil="true"/>
&lt;location nil="true"/>
&lt;/feed>
</pre>
</div>

View File

@ -0,0 +1,89 @@
<div class="row">
<div class="col-sm-offset-1 col-sm-3 col-xs-12" id="leftcol">
<%= render 'docs/sidebar' %>
</div>
<div class="col-sm-7 col-xs-12">
<h1 id="charts">Charts</h1>
The Charts API allows you to create an instant visualization of your data. The chart displays properly in all modern browsers and mobile devices. The chart can also show dynamic data by loading new data automatically.
<br><br>
Use the Charts API to present numerical data stored in ThingSpeak Channels on charts. Supported chart types are line, bar, column, and step. Options include size, color, and labels.
<br><br>
<hr />
<h2 id="create">Creating a Chart</h2>
To create a chart, open the following URL, replacing <span class="customcode">CHANNEL_ID</span> and <span class="customcode">FIELD_ID</span> with values from one of your Channels.
<br><br>
<pre>http://api.thingspeak.com/channels/<span class="customcode">CHANNEL_ID</span>/charts/<span class="customcode">FIELD_ID</span></pre>
<br>
Valid chart parameters:
<ul>
<li><b>title</b> (string) Chart title, default: Channel name (optional)</li>
<li><b>xaxis</b> (string) Chart's x-axis label, default: "Date" (optional)</li>
<li><b>yaxis</b> (string) Chart's y-axis label, default: field name (optional)</li>
<li><b>color</b> (string) Line color, default: red (optional)</li>
<li><b>bgcolor</b> (string) Background color, default: white (optional)</li>
<li><b>type</b> (line/bar/column) Type of chart, default: line (optional)</li>
<li><b>width</b> (integer) Chart width in pixels, iframe width will be 20px larger, default chart width: 400 (optional)</li>
<li><b>height</b> (integer) Chart height in pixels, iframe height will be 20px larger, default chart height: 200 (optional)</li>
<li><b>dynamic</b> (true/false) Make chart update automatically every 15 seconds, default: false (optional)</li>
<li><b>step</b> (true/false) Draw chart as a step chart, default: false (optional)</li>
<li><b>export</b> (true/false) Show export buttons, so that chart can be saved as an image, default: false (optional)</li>
</ul>
<br>
Valid feed parameters:
<ul>
<li><b>key</b> (string) Read API Key for this specific Channel (optional--no key required for public channels)</li>
<li><b>results</b> (integer) Number of entries to retrieve, 8000 max, default of 100 (optional)</li>
<li><b>days</b> (integer) Days from now to include in feed (optional)</li>
<li><b>start</b> (datetime) Start date in format YYYY-MM-DD%20HH:NN:SS (optional)</li>
<li><b>end</b> (datetime) End date in format YYYY-MM-DD%20HH:NN:SS (optional)</li>
<li><b>offset</b> (integer) Offset of your timezone without daylight savings time (optional)</li>
<li><b>status</b> (true/false) Include status updates in feed by setting "status=true" (optional)</li>
<li><b>location</b> (true/false) Include latitude, longitude, and elevation in feed by setting "location=true" (optional)</li>
<li><b>min</b> (decimal) Minimum value to include in response (optional)</li>
<li><b>max</b> (decimal) Maximum value to include in response (optional)</li>
<li><b>round</b> (integer) Round to this many decimal places (optional)</li>
<li><b>timescale</b> (integer or string) Get first value in this many minutes, valid values: 10, 15, 20, 30, 60, 240, 720, 1440, "daily" (optional)</li>
<li><b>sum</b> (integer or string) Get sum of this many minutes, valid values: 10, 15, 20, 30, 60, 240, 720, 1440, "daily" (optional)</li>
<li><b>average</b> (integer or string) Get average of this many minutes, valid values: 10, 15, 20, 30, 60, 240, 720, 1440, "daily" (optional)</li>
<li><b>median</b> (integer or string) Get median of this many minutes, valid values: 10, 15, 20, 30, 60, 240, 720, 1440, "daily" (optional)</li>
</ul>
<div class="alert alert-warning">
Please note that the results parameter is not compatible with timescale, sum, average, or median.
</div>
<br><br>
<hr />
<h2 id="embed">Embedding a Chart</h2>
To place a ThingSpeak Chart on your webpage, use the Chart API as the source of an iframe.
<br><br>
Chart Embed Code:
<br><br>
<pre>&lt;iframe width="450" height="250" style="border: 1px solid #cccccc;" src="http://thingspeak.com/channels/<span class="customcode">CHANNEL_ID</span>/charts/<span class="customcode">FIELD_ID</span>">&lt;/iframe></pre>
<br><br>
Example Chart:
<iframe width="100%" height="250" marginwidth="0" marginheight="0" scrolling="auto" frameborder="0" src="https://thingspeak.com/channels/3/charts/1?results=15"></iframe>
<br>
<h4>Embedding a Dynamic Chart</h4>
To place a dynamic ThingSpeak Chart on your webpage, use the Chart API as the source of an iframe and add the chart parameter ”dynamic=true”.
<br><br>
Chart Embed Code:
<br><br>
<pre>&lt;iframe width="450" height="250" style="border: 1px solid #cccccc;" src="http://thingspeak.com/channels/<span class="customcode">CHANNEL_ID</span>/charts/<span class="customcode">FIELD_ID</span>?dynamic=true">&lt;/iframe></pre>
<br><br>
Example Chart:
<iframe width="100%" height="250" marginwidth="0" marginheight="0" scrolling="auto" frameborder="0" src="https://thingspeak.com/channels/9/charts/1?dynamic=true&results=15&title=Dynamic+Light+Levels"></iframe>
<br><br><br><br><br><br><br><br><br><br><br><br>
</div>
</div>

View File

@ -0,0 +1,75 @@
<div class="row">
<div class="col-sm-offset-1 col-sm-3 col-xs-12" id="leftcol">
<%= render 'docs/sidebar' %>
</div>
<div class="col-sm-7 col-xs-12">
<div>
<%= render 'response' %>
<h2 id="errors">Error Codes</h2>
</div>
<br><br>
<table class="table table-striped table-bordered table-condensed">
<tr>
<th><%= t(:error_code) %></th>
<th><%= t(:error_http_status) %></th>
<th><%= t(:error_message) %></th>
<th><%= t(:error_details) %></th>
</tr>
<% t(:error_codes).each do |key, values| %>
<tr>
<td><code><%= key %></code></td>
<td><%= values[:http_status] %></td>
<td><%= values[:message] %></td>
<td><%= values[:details] %></td>
</tr>
<% end %>
</table>
<div>
All errors will be sent with their corresponding <a href="https://en.wikipedia.org/wiki/List_of_HTTP_status_codes" target="_blank">HTTP status code</a>.
<br><br>
</div>
<div class="format format-block format-text">
Example error response: <code>error_auth_required</code>
</div>
<div class="format format-block format-json">
The response will be a JSON error object, for example:
<pre class="prettyprint">
{
"status": "401",
"error":
{
"error_code": "error_auth_required",
"message": "Authorization Required",
"details": "Please make sure that your API key is correct."
}
}
</pre>
</div>
<div class="format format-block format-xml">
The response will be an XML error object, for example:
<pre class="prettyprint">
&lt;?xml version="1.0" encoding="UTF-8"?>
&lt;error>
&lt;error-code>error_auth_required&lt;/error-code>
&lt;message>Authorization Required&lt;/message>
&lt;details>Please make sure that your API key is correct.&lt;/details>
&lt;/error>
</pre>
</div>
</div>
</div>

View File

@ -1,4 +1,39 @@
<div class="row">
welcome
<div class="col-sm-offset-1 col-sm-3 col-xs-12" id="leftcol">
<%= render 'docs/sidebar' %>
</div>
<div class="col-sm-7 col-xs-12">
<h1>Getting Started</h1>
<ul>
<li>Sign Up for a New User Account -&nbsp;<a title="Create New ThingSpeak User Account" href="https://thingspeak.com/account/new">https://thingspeak.com/account/new</a></li>
<li>Create a New Channel by selecting <em>Channels</em> and then <em>Create New Channel</em></li>
<li>Follow a <a title="ThingSpeak Tutorials" href="http://community.thingspeak.com/tutorials/">tutorial</a> for common devices and applications</li>
</ul>
<br>
<h4>Support</h4>
<p>Please post your questions, comments, and feature requests in the <a title="ThingSpeak Forum" href="http://community.thingspeak.com/forum/">ThingSpeak Forum</a>.</p>
<br><br>
<h4>Open Source</h4>
<p>The ThingSpeak API is&nbsp;available&nbsp;on <a title="ThingSpeak Open Source Web of Things API on GitHub" href="https://github.com/iobridge/thingspeak" target="_blank">GitHub</a> and includes the complete ThingSpeak API for processing HTTP requests, storing numeric and&nbsp;alphanumeric&nbsp;data, numeric data processing, location tracking, and status updates. &nbsp;The open source version follows the same documentation as the ThingSpeak hosted service.</p>
<ul>
<li><a title="Open Source Web of Things API - ThingSpeak on GitHub" href="https://github.com/iobridge/thingspeak" target="_blank">GitHub Repository for ThingSpeak</a></li>
<li><a title="ThingSpeak GitHub Readme File" href="https://github.com/iobridge/thingspeak/blob/master/README.textile" target="_blank">ThingSpeak README and Setup Instructions</a></li>
</ul>
<br><br>
<h4>HTTP Headers</h4>
If you would like to reduce the number of HTTP headers sent by our application, please add the parameter "headers=false" to any HTTP request.
<br><br><br><br><br><br><br><br><br><br><br><br>
</div>
</div>

View File

@ -0,0 +1,26 @@
<div class="row">
<div class="col-sm-offset-1 col-sm-3 col-xs-12" id="leftcol">
<%= render 'docs/sidebar' %>
</div>
<div class="col-sm-7 col-xs-12">
<h1>Plugins</h1>
Plugins are way to create your applications natively on the ThingSpeak platform. You can create applications using HTML, CSS, and JavaScript to create mashups of ThingSpeak services with other web services. Plugins can either be private or public. Private plugins can only be viewed from a private ThingSpeak Channel view. Public Plugins can be embedded on your website or added to a ThingSpeak Channel view.
<br><br>
<hr />
<h2>Example Plugin</h2>
<p><a title="Display a Google Gauge Visualization using ThingSpeak Plugins" href="http://community.thingspeak.com/tutorials/google/display-a-google-gauge-visualization-using-thingspeak-plugins/">Display a Google Gauge Visualization using ThingSpeak Plugins</a> [<a title="Source ode for Google Gauge ThingSpeak Plugin" href="http://community.thingspeak.com/code/Google_Gauge.html">Source Code</a>]</p>
<p style="text-align: center;">
<iframe width="100%" height="120" marginwidth="0" marginheight="0" scrolling="auto" frameborder="0" src="http://community.thingspeak.com/code/Google_Gauge.html"></iframe>
</p>
<br><br><br><br><br><br><br><br><br><br><br><br>
</div>
</div>

View File

@ -0,0 +1,22 @@
<div class="row">
<div class="col-sm-offset-1 col-sm-3 col-xs-12" id="leftcol">
<%= render 'docs/sidebar' %>
</div>
<div class="col-sm-7 col-xs-12">
<h1 id="users">Users</h1>
<br><br>
<hr />
<%= render 'docs/users/show' %>
<hr />
<%= render 'docs/users/user_channels' %>
<br><br><br><br><br><br><br><br><br><br><br><br>
</div>
</div>

View File

@ -0,0 +1,59 @@
<div>
<%= render 'response' %>
<h2 id="get_user">Get User Information</h2>
</div>
<br>
To get a user's information, send an HTTP GET to <code><%= @ssl_api_domain %>users/<span class="customcode">USERNAME</span><span class="format format-json">.json</span><span class="format format-xml">.xml</span></code> ,
replacing <span class="customcode">USERNAME</span> with the person's username.
<br><br>
Valid parameters:
<ul>
<li><b>api_key</b> (string) - User's API Key which, if provided, will show private information such as email addresses. (optional)</li>
</ul>
<br>
Example GET:
<pre>
GET <span class="str"><%= @ssl_api_domain %>users/hans<span class="format format-json">.json</span><span class="format format-xml">.xml</span></span>
</pre>
<br>
<div class="format format-block format-text">
The response will be a webpage with information about the user.
</div>
<div class="format format-block format-json">
The response will be a JSON object of the user, for example:
<pre class="prettyprint">
{
"id": 4,
"login": "hans",
"created_at": "2010-12-03T09:17:52-05:00",
"bio": "Web Developer @iobridge, @thingspeak",
"website": "http://www.iamshadowlord.com"
}
</pre>
</div>
<div class="format format-block format-xml">
The response will be an XML object of the user, for example:
<pre class="prettyprint">
&lt;?xml version="1.0" encoding="UTF-8"?>
&lt;user>
&lt;id type="integer">4&lt;/id>
&lt;login>hans&lt;/login>
&lt;created-at type="dateTime">2010-12-03T09:17:52-05:00&lt;/created-at>
&lt;bio>Web Developer @iobridge, @thingspeak&lt;/bio>
&lt;website>http://www.iamshadowlord.com&lt;/website>
&lt;/user>
</pre>
</div>

View File

@ -0,0 +1,136 @@
<div>
<%= render 'response' %>
<h2 id="list_user_channels">List User's Channels</h2>
</div>
<br>
To get a list of a user's Channels, send an HTTP GET to <code><%= @ssl_api_domain %>users/<span class="customcode">USERNAME</span>/channels<span class="format format-json">.json</span><span class="format format-xml">.xml</span></code> ,
replacing <span class="customcode">USERNAME</span> with the person's username.
<br><br>
Valid parameters:
<ul>
<li><b>api_key</b> (string) - User's API Key which, if provided, will also show private channels. (optional)</li>
</ul>
<br>
Example GET:
<pre>
GET <span class="str"><%= @ssl_api_domain %>users/hans/channels<span class="format format-json">.json</span><span class="format format-xml">.xml</span></span>
</pre>
<br>
<div class="format format-block-xl format-text">
The response will be a webpage with a list of the user's Channels.
</div>
<div class="format format-block-xl format-json">
The response will be a JSON object of the user's Channels, for example:
<pre class="prettyprint">
{
"channels":
[
{
"id": 3,
"name": "ioBridge Server",
"description": "ioBridge IO-204 connected to web server",
"latitude": null,
"longitude": null,
"created_at": "2010-12-03T09:26:23-05:00",
"elevation": "",
"last_entry_id": 163690,
"ranking": 85,
"username": "hans",
"tags":
[
{
"id": 24,
"name": "temperature"
}
]
},
{
"id": 9,
"name": "my_house",
"description": "Netduino Plus connected to sensors around the house",
"latitude": "40.44",
"longitude": "-79.996",
"created_at": "2010-12-13T20:20:06-05:00",
"elevation": "",
"last_entry_id": 6062844,
"ranking": 100,
"username": "hans",
"tags":
[
{
"id": 9,
"name": "temp"
},
{
"id": 25,
"name": "light"
}
]
}
]
}
</pre>
</div>
<div class="format format-block-xl format-xml">
The response will be an XML object of the user's Channels, for example:
<pre class="prettyprint">
&lt;?xml version="1.0" encoding="UTF-8"?>
&lt;response>
&lt;channels type="array">
&lt;channel>
&lt;id type="integer">3&lt;/id>
&lt;name>ioBridge Server&lt;/name>
&lt;description>ioBridge IO-204 connected to web server&lt;/description>
&lt;latitude nil="true"/>
&lt;longitude nil="true"/>
&lt;created-at type="dateTime">2010-12-03T09:26:23-05:00&lt;/created-at>
&lt;elevation/>
&lt;last-entry-id type="integer">163690&lt;/last-entry-id>
&lt;ranking type="integer">85&lt;/ranking>
&lt;username>hans&lt;/username>
&lt;tags type="array">
&lt;tag>
&lt;id type="integer">24&lt;/id>
&lt;name>temperature&lt;/name>
&lt;/tag>
&lt;/tags>
&lt;/channel>
&lt;channel>
&lt;id type="integer">9&lt;/id>
&lt;name>my_house&lt;/name>
&lt;description>Netduino Plus connected to sensors around the house&lt;/description>
&lt;latitude type="decimal">40.44&lt;/latitude>
&lt;longitude type="decimal">-79.996&lt;/longitude>
&lt;created-at type="dateTime">2010-12-13T20:20:06-05:00&lt;/created-at>
&lt;elevation/>
&lt;last-entry-id type="integer">6062860&lt;/last-entry-id>
&lt;ranking type="integer">100&lt;/ranking>
&lt;username>hans&lt;/username>
&lt;tags type="array">
&lt;tag>
&lt;id type="integer">9&lt;/id>
&lt;name>temp&lt;/name>
&lt;/tag>
&lt;tag>
&lt;id type="integer">25&lt;/id>
&lt;name>light&lt;/name>
&lt;/tag>
&lt;/tags>
&lt;/channel>
&lt;/channels>
&lt;/response>
</pre>
</div>

View File

@ -6,7 +6,7 @@
<a href="http://www.thingspeak.com/" title="Open Source Internet of Things">ThingSpeak.com</a> |
<a href="http://community.thingspeak.com/" title="The ThingSpeak Blog covering the web of things">Blog</a> |
<a href="http://community.thingspeak.com/forum/" title="Open discussion forum for ThingSpeak">Forum</a> |
<a href="http://community.thingspeak.com/documentation/" title="ThingSpeak Documentation">Documentation</a> |
<a href="/docs" title="ThingSpeak Documentation">Documentation</a> |
<a href="http://community.thingspeak.com/tutorials/" title="Tutorials on how to use ThingSpeak web services">Tutorials</a> |
<a href="http://feeds.feedburner.com/internetofthings" title="Internet of Things RSS Feed" target="_blank">RSS Feed</a>
</span>

View File

@ -34,6 +34,9 @@
<li><%= link_to t(:profile_edit), edit_profile_path %></li>
</ul>
</li>
<% if current_admin_user.present? %>
<li><%= link_to t(:admin), "/admin" %></li>
<% end %>
<% else %>
<li <%= "class=active" if @menu == 'channels' %>><%= link_to t(:channels), public_channels_path %></li>
@ -43,10 +46,10 @@
</ul>
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<li class="<%= "active " if @menu == 'support' %>dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><%=t(:support)%> <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="http://community.thingspeak.com/documentation/"><%= t(:documentation) %></a></li>
<li><a href="/docs"><%= t(:documentation) %></a></li>
<li><a href="http://community.thingspeak.com/tutorials/"><%= t(:tutorials) %></a></li>
<li><a href="http://community.thingspeak.com/forum/"><%= t(:forum) %></a></li>
</ul>
@ -58,7 +61,7 @@
<li><%= link_to t(:signout), logout_path %></li>
<% else %>
<li><%= link_to t(:signin), login_path %></li>
<li><%= link_to t(:signup), new_account_path %></li>
<li><%= link_to t(:signup), new_user_registration_path %></li>
<% end %>
</ul>

View File

@ -23,6 +23,12 @@
<div id="wrap">
<%= render 'layouts/header' %>
<div class="container">
<% if notice.present? %>
<p class="flash alert alert-success"><%= notice %><span class="dismiss">X</span></p>
<% end %>
<% if alert.present? %>
<p class="flash alert alert-danger"><%= alert %><span class="dismiss">X</span></p>
<% end %>
<%= yield %>
</div>
</div>

View File

@ -6,11 +6,11 @@
<body>
<p>
<%= t(:password_reset_message1) %>
<br />
<br>
<%= t(:password_reset_message2) %>
<br />
<br>
<%= t(:password_reset_message3) %>
<br /><br />
<br><br>
<a href="<%= @webpage %>"><%= @webpage %></a>
</p>
</body>

View File

@ -111,6 +111,6 @@
</script>
<br />
<br>
<div id="chart-container" style="width: 500px; height: 260px; padding-left: 20px; float: left;"></div>

View File

@ -1,7 +1,8 @@
<table border="1">
<% for header in request.env.select {|k,v| k.match("^HTTP.*")} %>
<tr>
<td><%=header[0].split('_',2)[1]%></td><td><%=header[1]%></td>
<td><%=header[0].split('_',2)[1]%></td><td><%=header[1]%></td>
</tr>
<% end %>
</table>

View File

@ -203,7 +203,7 @@
<li><a href="http://www.twitter.com/thingspeak" title="Follow ThingSpeak on Twitter">Twitter</a></li>
<li><a href="https://github.com/iobridge/ThingSpeak" title="Open Source Internet of Things Project on GitHub">GitHub</a></li>
<li><a href="http://community.thingspeak.com" title="Join the ThingSpeak Community">ThingSpeak Community</a></li>
<li><a href="http://community.thingspeak.com/documentation" title="ThingSpeak API and Apps Documentation">Documentation</a> and <a href="http://community.thingspeak.com/tutorials" title="ThingSpeak Tutorials">Tutorials</a></li>
<li><a href="/docs" title="ThingSpeak API and Apps Documentation">Documentation</a> and <a href="http://community.thingspeak.com/tutorials" title="ThingSpeak Tutorials">Tutorials</a></li>
<li><a href="mailto:support@thingspeak.com" title="Contact ThingSpeak">Questions</a></li>
</ul>
</p>

View File

@ -87,10 +87,10 @@
<img src='<%= "#{@api_domain}images/social_sensor_network_main.png "%>' width='600' height='415' border='0' />
<% end %>
<h2>the social sensor network is forming.</h2>
<h3>We have one thing in common. At almost all times we are connected to the web.<br />
<h3>We have one thing in common. At almost all times we are connected to the web.<br>
The Social Sensor Network allows everyone to report and share data in an open and meaningful way.</h3>
<br />
<br>
<form method=post action="https://app.icontact.com/icp/signup.php" name="icpsignup" id="icpsignup2312" accept-charset="UTF-8" onsubmit="return verifyRequired2312();" >
<input type=hidden name=redirect value="http://www.socialsensornetwork.com?invite=success" />
@ -128,12 +128,12 @@
}
</script>
<br />
<br />
<br>
<br>
<span id="demo_text">demo channels</span>
<br />
<br>
<span id="demo"><a href="http://www.socialsensornetwork.com/irs-refund">IRS Refunds</a></span>
<span id="demo"><a href="http://www.socialsensornetwork.com/online-poker">Frozen Online Poker Money</a></span>

View File

@ -1,7 +1,7 @@
<h2>Pipes</h2>
<%= link_to 'New Pipe', new_pipe_path %>
<br /><br />
<br><br>
<table class="nicetable">
<tr class="header"><td>ID</td><td>Name</td><td>Slug</td><td>URL</td><td>Date</td></tr>
@ -15,5 +15,5 @@
</tr>
<% end %>
</table>
<br />
<br>
<%= will_paginate @pipes %>

View File

@ -48,7 +48,7 @@
<% end %>
<br /><br />
<br><br>
<h3><%= t(:plugin_delete_message) %></h3>
<%= button_to t(:plugin_delete), plugin_path(@plugin.id), :method => 'delete', :data => { :confirm => t(:confirm_plugin_delete) }, :class => 'btn btn-danger' %>

View File

@ -23,7 +23,7 @@
</table>
<br /><br />
<br><br>
<% end %>
<%= form_for :plugin do |p| %>

View File

@ -8,8 +8,8 @@
<body>
<div class="recent_status">
<% @statuses.each do |r| %>
<div ><span class="status_messages" id="<%= r.entry_id %>"><%= r.status %><br /><abbr class="timeago" title="<%= r.created_at %>"><%= r.created_at %></abbr></div>
<br />
<div ><span class="status_messages" id="<%= r.entry_id %>"><%= r.status %><br><abbr class="timeago" title="<%= r.created_at %>"><%= r.created_at %></abbr></div>
<br>
<% end %>
<div>
<script>
@ -31,7 +31,7 @@ function refreshStatus() {
$(".recent_status").prepend("<div><span class=\"status_messages\" id=\"" +
data[i].entry_id + "\">"+
data[i].status +
"</span><br /><abbr class=\"timeago\" title=\"" +
"</span><br><abbr class=\"timeago\" title=\"" +
data[i].created_at + "\">" +
data[i].created_at + "</abbr></div></br>");
}

View File

@ -6,14 +6,14 @@
<td><%= t.text_field :name %><%= t.submit t(:submit) %></td>
</tr>
</table>
<br />
<br>
<% if @tag_name %>
<% if @results %>
<% @results.each do |channel| %>
<%= link_to channel.name, channel_path(channel.id) %>
<br />
<br>
<% end %>
<% else %>

View File

@ -1,54 +1,55 @@
<div class="col-sm-8 col-xs-7">
<div class="row">
<div class="col-sm-8 col-xs-7">
<ol class="breadcrumb">
<li><%= link_to t(:myaccount), account_path %></li>
<li class="active"><%= t(:account_edit) %></li>
</ol>
<ol class="breadcrumb">
<li><%= link_to t(:myaccount), account_path %></li>
<li class="active"><%= t(:account_edit) %></li>
</ol>
<%= form_for @user, :url => account_path, :html => {:class => 'form-horizontal'} do |f| %>
<%= error_messages_for 'user', :header_message => t(:try_again), :message => t(:account_error_edit) %>
<input name='userlogin' class='userlogin' />
<%= form_for @user, :url => account_path, :html => {:class => 'form-horizontal'} do |f| %>
<%= error_messages_for 'user', :header_message => t(:try_again), :message => t(:account_error_edit) %>
<input name='userlogin' class='userlogin' />
<div class="form-group">
<label class="col-sm-4 col-xs-4 control-label"><%= t(:userid) %></label>
<div class="col-sm-8 col-xs-8"><%= f.text_field :login, :class => 'form-control' %></div>
</div>
<div class="form-group">
<label class="col-sm-4 col-xs-4 control-label"><%= t(:email) %></label>
<div class="col-sm-8 col-xs-8"><%= f.text_field :email, :class => 'form-control' %></div>
</div>
<div class="form-group">
<label class="col-sm-4 col-xs-4 control-label"><%= t(:time_zone) %></label>
<div class="col-sm-8 col-xs-8">
<p class="form-control-static">
<%= time_zone_select 'user', 'time_zone', nil, {:default => 'Eastern Time (US & Canada)'}, {:class => 'form-control'} %>
</p>
<div class="form-group">
<label class="col-sm-4 col-xs-4 control-label"><%= t(:userid) %></label>
<div class="col-sm-8 col-xs-8"><%= f.text_field :login, :class => 'form-control' %></div>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 col-xs-4 control-label"><%= t(:password_change) %></label>
<div class="col-sm-8 col-xs-8"><%= f.password_field :password, :class => 'form-control' %></div>
</div>
<div class="form-group">
<label class="col-sm-4 col-xs-4 control-label"><%= t(:email) %></label>
<div class="col-sm-8 col-xs-8"><%= f.text_field :email, :class => 'form-control' %></div>
</div>
<div class="form-group">
<label class="col-sm-4 col-xs-4 control-label"><%= t(:time_zone) %></label>
<div class="col-sm-8 col-xs-8">
<p class="form-control-static">
<%= time_zone_select 'user', 'time_zone', nil, {:default => 'Eastern Time (US & Canada)'}, {:class => 'form-control'} %>
</p>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 col-xs-4 control-label"><%= t(:password_confirmation) %></label>
<div class="col-sm-8 col-xs-8"><%= f.password_field :password_confirmation, :class => 'form-control' %></div>
</div>
<div class="form-group">
<label class="col-sm-4 col-xs-4 control-label"><%= t(:password_change) %></label>
<div class="col-sm-8 col-xs-8"><%= f.password_field :password, :class => 'form-control' %></div>
</div>
<div class="form-group">
<label class="col-sm-4 col-xs-4 control-label"><%= t(:password_current) %></label>
<div class="col-sm-8 col-xs-8"><%= f.password_field :password_current, :class => 'form-control' %></div>
</div>
<div class="form-group">
<label class="col-sm-4 col-xs-4 control-label"><%= t(:password_current) %></label>
<div class="col-sm-8 col-xs-8"><%= f.password_field :password_current, :class => 'form-control' %></div>
</div>
<div class="form-group">
<div class="col-sm-offset-4 col-sm-8 col-xs-offset-4 col-xs-8"><p class="form-control-static"><%= f.submit t(:account_update), :class => 'btn btn-primary' %></p></div>
</div>
<div class="form-group">
<div class="col-sm-offset-4 col-sm-8 col-xs-offset-4 col-xs-8"><p class="form-control-static"><%= f.submit t(:account_update), :class => 'btn btn-primary' %></p></div>
</div>
<% end %>
<% end %>
</div>
</div>
<br><br>
<h3><%= t(:account_delete_message) %></h3>
<%= button_to t(:account_delete), user_path(@user.id), :method => 'delete', :data => { :confirm => t(:confirm_account_delete) }, :class => 'btn btn-danger' %>

View File

@ -11,7 +11,7 @@
</tr>
<% end %>
</table>
<br />
<br>
<%= will_paginate @channels %>
<% else %>
<%= t(:user_no_public_channels) %>

View File

@ -1,17 +1,17 @@
<% if @user.public_flag == true %>
<div class="FR">
<br /><br />
<br><br>
<%= gravatar_tag @user, :default => 'wavatar', :secure => true %>
</div>
<h2><%= t(:profile_for) %> <%= @user.login %></h2>
<%= link_to t(:channels_public_view), list_channels_path(@user.login) %>
<br /><br />
<br><br>
<%= t(:member_since) %> <%= @user.created_at.strftime('%B %-d, %Y') %>
<br /><br />
<br><br>
<%= t(:profile_website) %>: <%= link_to @user.website, @user.website %>
<br /><br />
<br><br>
<%= t(:profile_bio) %>: <%= @user.bio %>
<% else %>

View File

@ -4,7 +4,7 @@
<div class="col-pad">
<%= link_to t(:account_edit), edit_account_path, :class => 'btn btn-primary btn-sm' %>
<br /><br />
<br><br>
<table class="table">
<tr>
<td><%= t(:userid) %></td>
@ -29,13 +29,13 @@
</table>
</div>
<br /><br />
<br><br>
<h4 class="breadcrumb"><%= t(:public_profile) %></h4>
<div class="col-pad">
<%= link_to t(:profile_edit), edit_profile_path, :class => 'btn btn-primary btn-sm' %>
<br /><br />
<br><br>
<%= link_to t(:profile_view), user_profile_path(@user.login) %>
<br /><br />
<br><br>
<% if @user.public_flag %>
<table class="table">
<tr>

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