initial checkin of full application
This commit is contained in:
45
app/controllers/api_keys_controller.rb
Normal file
45
app/controllers/api_keys_controller.rb
Normal file
@ -0,0 +1,45 @@
|
||||
class ApiKeysController < ApplicationController
|
||||
before_filter :require_user, :set_channels_menu
|
||||
|
||||
def index
|
||||
get_channel_data
|
||||
@read_keys = ApiKey.find(:all, :conditions => { :channel_id => @channel.id, :user_id => current_user.id, :write_flag => 0 })
|
||||
end
|
||||
|
||||
def destroy
|
||||
@api_key = ApiKey.find_by_api_key(params[:api_key])
|
||||
@api_key.delete if @api_key.user_id == current_user.id
|
||||
redirect_to :back
|
||||
end
|
||||
|
||||
def create
|
||||
@channel = Channel.find(params[:channel_id])
|
||||
# make sure channel belongs to current user
|
||||
check_permissions(@channel)
|
||||
|
||||
@api_key = ApiKey.find(:first, :conditions => { :channel_id => @channel.id, :user_id => current_user.id, :write_flag => 1 } )
|
||||
|
||||
# if no api key found or read api key
|
||||
if (@api_key.nil? or params[:write] == '0')
|
||||
@api_key = ApiKey.new
|
||||
@api_key.channel_id = @channel.id
|
||||
@api_key.user_id = current_user.id
|
||||
@api_key.write_flag = params[:write]
|
||||
end
|
||||
|
||||
# set new api key and save
|
||||
@api_key.api_key = generate_api_key
|
||||
@api_key.save
|
||||
|
||||
# redirect
|
||||
redirect_to channel_api_keys_path(@channel.id) and return
|
||||
end
|
||||
|
||||
def update
|
||||
@api_key = ApiKey.find_by_api_key(params[:api_key][:api_key])
|
||||
|
||||
@api_key.note = params[:api_key][:note]
|
||||
@api_key.save if current_user.id == @api_key.user_id
|
||||
redirect_to channel_api_keys_path(@api_key.channel)
|
||||
end
|
||||
end
|
166
app/controllers/application_controller.rb
Normal file
166
app/controllers/application_controller.rb
Normal file
@ -0,0 +1,166 @@
|
||||
class ApplicationController < ActionController::Base
|
||||
# include all helpers for controllers
|
||||
helper :all
|
||||
# include these helper methods for views
|
||||
helper_method :current_user_session, :current_user, :get_header_value
|
||||
protect_from_forgery
|
||||
before_filter :set_variables
|
||||
|
||||
# set up some variables across the entire application
|
||||
def set_variables
|
||||
# hard code locale as english
|
||||
I18n.locale = 'en'
|
||||
# sets timezone for current user, all DateTime outputs will be automatically formatted
|
||||
Time.zone = current_user.time_zone if current_user
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_channels_menu
|
||||
@menu = 'channels'
|
||||
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
|
||||
|
||||
# check that user is logged in
|
||||
def require_user
|
||||
if current_user.nil?
|
||||
redirect_to login_path
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def require_no_user
|
||||
if current_user
|
||||
store_location
|
||||
redirect_to account_path
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def store_location
|
||||
if params[:controller] != "user_sessions"
|
||||
session[:return_to] = request.fullpath
|
||||
end
|
||||
end
|
||||
|
||||
def redirect_back_or_default(default)
|
||||
redirect_to(session[:return_to] || default)
|
||||
session[:return_to] = nil
|
||||
end
|
||||
|
||||
def domain
|
||||
u = request.url
|
||||
begin
|
||||
# the number 12 is the position at which to begin searching for '/', so we don't get the intitial '/' from http://
|
||||
u = u[0..u.index('/', 12)]
|
||||
rescue
|
||||
u += '/'
|
||||
end
|
||||
# uncomment the line below for https support in a production environment
|
||||
#u = u.sub(/http:/, 'https:') if Rails.env == 'production'
|
||||
return u
|
||||
end
|
||||
|
||||
# gets the api key
|
||||
def get_userkey
|
||||
return get_header_value('THINGSPEAKAPIKEY') || params[:key] || params[:api_key] || params[:apikey]
|
||||
end
|
||||
|
||||
# get specified header value
|
||||
def get_header_value(name)
|
||||
value = nil
|
||||
for header in request.env
|
||||
value = header[1] if (header[0].upcase.index(name.upcase))
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
# gets the same data for showing or editing
|
||||
def get_channel_data
|
||||
@channel = Channel.find(params[:channel_id]) if params[:channel_id]
|
||||
@channel = Channel.find(params[:id]) if @channel.nil? and params[:id]
|
||||
@key = ''
|
||||
# make sure channel belongs to current user
|
||||
check_permissions(@channel)
|
||||
|
||||
@api_key = ApiKey.find(:first, :conditions => { :channel_id => @channel.id, :user_id => current_user.id, :write_flag => 1 } )
|
||||
@key = @api_key.api_key if @api_key
|
||||
end
|
||||
|
||||
def check_permissions(channel)
|
||||
render :text => t(:channel_permission) and return if (current_user.nil? || (channel.user_id != current_user.id))
|
||||
end
|
||||
|
||||
# checks permission for channel using api_key
|
||||
def channel_permission?(channel, api_key)
|
||||
if channel.public_flag or (api_key and api_key.channel_id == channel.id) or (current_user and channel.user_id == current_user.id)
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
# outputs error for bad channel
|
||||
def bad_channel_xml
|
||||
channel_unauthorized = Channel.new
|
||||
channel_unauthorized.id = -1
|
||||
return channel_unauthorized.to_xml(:only => :id)
|
||||
end
|
||||
|
||||
# outputs error for bad feed
|
||||
def bad_feed_xml
|
||||
feed_unauthorized = Feed.new
|
||||
feedl_unauthorized.id = -1
|
||||
return feed_unauthorized.to_xml(:only => :entry_id)
|
||||
end
|
||||
|
||||
# generates a database unique api key
|
||||
def generate_api_key(size = 16)
|
||||
alphanumerics = ('0'..'9').to_a + ('A'..'Z').to_a
|
||||
k = (0..size).map {alphanumerics[Kernel.rand(36)]}.join
|
||||
|
||||
# if key exists in database, regenerate key
|
||||
k = generate_api_key if ApiKey.find_by_api_key(k)
|
||||
|
||||
# output the key
|
||||
return k
|
||||
end
|
||||
|
||||
# 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)
|
||||
|
||||
start_date = Time.now - 1.day
|
||||
end_date = Time.now
|
||||
start_date = (Time.now - params[:days].to_i.days) if params[:days]
|
||||
start_date = DateTime.strptime(params[:start]) if params[:start]
|
||||
end_date = DateTime.strptime(params[:end]) if params[:end]
|
||||
date_range = (start_date..end_date)
|
||||
# only get a maximum of 30 days worth of data
|
||||
date_range = (end_date - 30.days..end_date) if (end_date - start_date) > 30.days
|
||||
|
||||
return date_range
|
||||
end
|
||||
|
||||
def set_time_zone(params)
|
||||
# set timezone correctly
|
||||
if params[:offset]
|
||||
Time.zone = params[:offset].to_i
|
||||
elsif current_user
|
||||
Time.zone = current_user.time_zone
|
||||
else
|
||||
Time.zone = 0
|
||||
end
|
||||
end
|
||||
|
||||
end
|
125
app/controllers/channels_controller.rb
Normal file
125
app/controllers/channels_controller.rb
Normal file
@ -0,0 +1,125 @@
|
||||
class ChannelsController < ApplicationController
|
||||
before_filter :require_user, :except => [ :show, :post_data ]
|
||||
before_filter :set_channels_menu
|
||||
protect_from_forgery :except => :post_data
|
||||
|
||||
def index
|
||||
@channels = current_user.channels
|
||||
end
|
||||
|
||||
def show
|
||||
@channel = Channel.find(params[:id]) if params[:id]
|
||||
|
||||
# if owner of channel
|
||||
get_channel_data if current_user and @channel.user_id == current_user.id
|
||||
end
|
||||
|
||||
def edit
|
||||
get_channel_data
|
||||
end
|
||||
|
||||
def update
|
||||
@channel = Channel.find(params[:id])
|
||||
# make sure channel belongs to current user
|
||||
check_permissions(@channel)
|
||||
# protect against bots
|
||||
render :text => '' and return if params[:userlogin].length > 0
|
||||
|
||||
@channel.update_attributes(params[:channel])
|
||||
@channel.name = "#{t(:channel_default_name)} #{@channel.id}" if params[:channel][:name].empty?
|
||||
@channel.save
|
||||
redirect_to channel_path(@channel.id) and return
|
||||
end
|
||||
|
||||
def create
|
||||
# protect against bots
|
||||
render :text => '' and return if params[:userlogin].length > 0
|
||||
|
||||
# get default name for field
|
||||
@d = t(:channel_default_field)
|
||||
|
||||
# add channel with defaults
|
||||
@channel = Channel.new(:field1 => "#{@d}1")
|
||||
@channel.user_id = current_user.id
|
||||
@channel.save
|
||||
|
||||
# now that the channel is saved, we can create the default name
|
||||
@channel.name = "#{t(:channel_default_name)} #{@channel.id}"
|
||||
@channel.save
|
||||
|
||||
# create an api key for this channel
|
||||
@api_key = ApiKey.new
|
||||
@api_key.channel_id = @channel.id
|
||||
@api_key.user_id = current_user.id
|
||||
@api_key.write_flag = 1
|
||||
@api_key.api_key = generate_api_key
|
||||
@api_key.save
|
||||
|
||||
# redirect to edit the newly created channel
|
||||
redirect_to edit_channel_path(@channel.id)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@channel = Channel.find(params[:id])
|
||||
# make sure channel belongs to current user
|
||||
check_permissions(@channel)
|
||||
|
||||
# do the delete
|
||||
@channel.delete
|
||||
redirect_to channels_path
|
||||
end
|
||||
|
||||
# response is '0' if failure, 'entry_id' if success
|
||||
def post_data
|
||||
status = '0'
|
||||
feed = Feed.new
|
||||
|
||||
api_key = ApiKey.find_by_api_key(get_userkey)
|
||||
|
||||
# if write persmission, allow post
|
||||
if (api_key && api_key.write_flag)
|
||||
channel = Channel.find(api_key.channel_id)
|
||||
|
||||
# update entry_id for channel and feed
|
||||
entry_id = channel.last_entry_id.nil? ? 1 : channel.last_entry_id + 1
|
||||
channel.last_entry_id = entry_id
|
||||
feed.entry_id = entry_id
|
||||
|
||||
# try to get created_at datetime if appropriate
|
||||
if params[:created_at]
|
||||
begin
|
||||
@feed.created_at = DateTime.parse(params[:created_at])
|
||||
# if invalid datetime, don't do anything--rails will set created_at
|
||||
rescue
|
||||
end
|
||||
end
|
||||
|
||||
# strip line feeds from end of parameters
|
||||
params.each do |key, value|
|
||||
params[key] = value.sub(/\\n$/, '').sub(/\\r$/, '')
|
||||
end
|
||||
|
||||
# set feed details
|
||||
feed.channel_id = channel.id
|
||||
feed.raw_data = params
|
||||
feed.field1 = params[:field1] if params[:field1]
|
||||
feed.field2 = params[:field2] if params[:field2]
|
||||
feed.field3 = params[:field3] if params[:field3]
|
||||
feed.field4 = params[:field4] if params[:field4]
|
||||
feed.field5 = params[:field5] if params[:field5]
|
||||
feed.field6 = params[:field6] if params[:field6]
|
||||
feed.field7 = params[:field7] if params[:field7]
|
||||
feed.field8 = params[:field8] if params[:field8]
|
||||
feed.status = params[:status] if params[:status]
|
||||
|
||||
if channel.save && feed.save
|
||||
status = entry_id
|
||||
end
|
||||
end
|
||||
|
||||
# output response code
|
||||
render :text => '0', :status => 400 and return if status == '0'
|
||||
render :text => status
|
||||
end
|
||||
|
||||
end
|
78
app/controllers/charts_controller.rb
Normal file
78
app/controllers/charts_controller.rb
Normal file
@ -0,0 +1,78 @@
|
||||
class ChartsController < ApplicationController
|
||||
|
||||
def index
|
||||
set_channels_menu
|
||||
@channel = Channel.find(params[:channel_id])
|
||||
@channel_id = params[:channel_id]
|
||||
@domain = domain
|
||||
|
||||
# default chart size
|
||||
@width = default_width
|
||||
@height = default_height
|
||||
|
||||
check_permissions(@channel)
|
||||
end
|
||||
|
||||
def show
|
||||
# allow these parameters when creating feed querystring
|
||||
feed_params = ['key','days','start','end','round','timescale','average','median','sum']
|
||||
|
||||
# default chart size
|
||||
@width = default_width
|
||||
@height = default_height
|
||||
|
||||
# add extra parameters to querystring
|
||||
@qs = ''
|
||||
params.each do |p|
|
||||
@qs += "&#{p[0]}=#{p[1]}" if feed_params.include?(p[0])
|
||||
end
|
||||
|
||||
# fix chart colors if necessary
|
||||
params[:color] = fix_color(params[:color])
|
||||
params[:bgcolor] = fix_color(params[:bgcolor])
|
||||
|
||||
@domain = domain
|
||||
render :layout => false
|
||||
end
|
||||
|
||||
# save chart options
|
||||
def update
|
||||
@channel = Channel.find(params[:channel_id])
|
||||
@status = 0
|
||||
|
||||
# check permissions
|
||||
if @channel.user_id == current_user.id
|
||||
|
||||
# save data
|
||||
@channel["options#{params[:id]}"] = params[:options]
|
||||
if @channel.save
|
||||
@status = 1
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# return response: 1=success, 0=failure
|
||||
render :json => @status.to_json
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def default_width
|
||||
450
|
||||
end
|
||||
|
||||
def default_height
|
||||
250
|
||||
end
|
||||
|
||||
# fixes chart color if user forgets the leading '#'
|
||||
def fix_color(color)
|
||||
# check for 3 or 6 character hexadecimal value
|
||||
if (color and color.match(/^([0-9]|[a-f]|[A-F]){3}(([0-9]|[a-f]|[A-F]){3})?$/))
|
||||
color = '#' + color
|
||||
end
|
||||
|
||||
return color
|
||||
end
|
||||
|
||||
end
|
501
app/controllers/feed_controller.rb
Normal file
501
app/controllers/feed_controller.rb
Normal file
@ -0,0 +1,501 @@
|
||||
class FeedController < ApplicationController
|
||||
require 'csv'
|
||||
|
||||
def index
|
||||
channel = Channel.find(params[:channel_id])
|
||||
api_key = ApiKey.find_by_api_key(get_userkey)
|
||||
@success = channel_permission?(channel, api_key)
|
||||
|
||||
# check for access
|
||||
if @success
|
||||
# create options hash
|
||||
channel_options = { :only => channel_select_data(channel) }
|
||||
select_options = feed_select_data(channel)
|
||||
|
||||
# get feed based on conditions
|
||||
feeds = Feed.find(
|
||||
:all,
|
||||
:conditions => { :channel_id => channel.id, :created_at => get_date_range(params) },
|
||||
:select => select_options,
|
||||
:order => 'created_at'
|
||||
)
|
||||
|
||||
# if a feed has data
|
||||
if !feeds.empty?
|
||||
# convert to timescales if necessary
|
||||
if timeparam_valid?(params[:timescale])
|
||||
feeds = feeds_into_timescales(feeds)
|
||||
# convert to sums if necessary
|
||||
elsif timeparam_valid?(params[:sum])
|
||||
feeds = feeds_into_sums(feeds)
|
||||
# convert to averages if necessary
|
||||
elsif timeparam_valid?(params[:average])
|
||||
feeds = feeds_into_averages(feeds)
|
||||
# convert to medians if necessary
|
||||
elsif timeparam_valid?(params[:median])
|
||||
feeds = feeds_into_medians(feeds)
|
||||
end
|
||||
end
|
||||
|
||||
# set output correctly
|
||||
if params[:format] == 'xml'
|
||||
@channel_output = channel.to_xml(channel_options).sub('</channel>', '').strip
|
||||
@feed_output = feeds.to_xml(:skip_instruct => true).gsub(/\n/, "\n ").chop.chop
|
||||
elsif params[:format] == 'csv'
|
||||
@feed_output = feeds
|
||||
else
|
||||
@channel_output = channel.to_json(channel_options).chop
|
||||
@feed_output = feeds.to_json
|
||||
end
|
||||
|
||||
# else no access, set error code
|
||||
else
|
||||
if params[:format] == 'xml'
|
||||
@channel_output = bad_channel_xml
|
||||
else
|
||||
@channel_output = '-1'.to_json
|
||||
end
|
||||
end
|
||||
|
||||
# set callback for jsonp
|
||||
@callback = params[:callback] if params[:callback]
|
||||
|
||||
# set csv headers if necessary
|
||||
@csv_headers = select_options if params[:format] == 'csv'
|
||||
|
||||
# output proper http response if error
|
||||
render :text => '-1', :status => 400 and return if !@success
|
||||
|
||||
# output data in proper format
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json
|
||||
format.xml
|
||||
format.csv
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@channel = Channel.find(params[:channel_id])
|
||||
@api_key = ApiKey.find_by_api_key(get_userkey)
|
||||
output = '-1'
|
||||
|
||||
# get most recent entry if necessary
|
||||
params[:id] = @channel.last_entry_id if params[:id] == 'last'
|
||||
|
||||
# set timezone correctly
|
||||
set_time_zone(params)
|
||||
|
||||
@feed = Feed.find(
|
||||
:first,
|
||||
:conditions => { :channel_id => @channel.id, :entry_id => params[:id] },
|
||||
:select => feed_select_data(@channel)
|
||||
)
|
||||
@success = channel_permission?(@channel, @api_key)
|
||||
|
||||
# check for access
|
||||
if @success
|
||||
# set output correctly
|
||||
if params[:format] == 'xml'
|
||||
output = @feed.to_xml
|
||||
elsif params[:format] == 'csv'
|
||||
@csv_headers = feed_select_data(@channel)
|
||||
elsif (params[:format] == 'txt' or params[:format] == 'text')
|
||||
output = add_prepend_append(@feed["field#{params[:field_id]}"])
|
||||
else
|
||||
output = @feed.to_json
|
||||
end
|
||||
# else set error code
|
||||
else
|
||||
if params[:format] == 'xml'
|
||||
output = bad_feed_xml
|
||||
else
|
||||
output = '-1'.to_json
|
||||
end
|
||||
end
|
||||
|
||||
# output data in proper format
|
||||
respond_to do |format|
|
||||
format.html { render :json => output }
|
||||
format.json { render :json => output, :callback => params[:callback] }
|
||||
format.xml { render :xml => output }
|
||||
format.csv
|
||||
format.text { render :text => output }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# only output these fields for channel
|
||||
def channel_select_data(channel)
|
||||
only = [:name, :created_at, :updated_at, :id, :last_entry_id]
|
||||
only += [:description] unless channel.description.blank?
|
||||
only += [:latitude] unless channel.latitude.blank?
|
||||
only += [:longitude] unless channel.longitude.blank?
|
||||
only += [:elevation] unless channel.elevation.blank?
|
||||
only += [:field1] unless channel.field1.blank?
|
||||
only += [:field2] unless channel.field2.blank?
|
||||
only += [:field3] unless channel.field3.blank?
|
||||
only += [:field4] unless channel.field4.blank?
|
||||
only += [:field5] unless channel.field5.blank?
|
||||
only += [:field6] unless channel.field6.blank?
|
||||
only += [:field7] unless channel.field7.blank?
|
||||
only += [:field8] unless channel.field8.blank?
|
||||
|
||||
return only
|
||||
end
|
||||
|
||||
# only output these fields for feed
|
||||
def feed_select_data(channel)
|
||||
only = [:created_at]
|
||||
only += [:entry_id] unless timeparam_valid?(params[:timescale]) or timeparam_valid?(params[:average]) or timeparam_valid?(params[:median]) or timeparam_valid?(params[:sum])
|
||||
only += [:field1] unless channel.field1.blank? or (params[:field_id] and params[:field_id] != '1')
|
||||
only += [:field2] unless channel.field2.blank? or (params[:field_id] and params[:field_id] != '2')
|
||||
only += [:field3] unless channel.field3.blank? or (params[:field_id] and params[:field_id] != '3')
|
||||
only += [:field4] unless channel.field4.blank? or (params[:field_id] and params[:field_id] != '4')
|
||||
only += [:field5] unless channel.field5.blank? or (params[:field_id] and params[:field_id] != '5')
|
||||
only += [:field6] unless channel.field6.blank? or (params[:field_id] and params[:field_id] != '6')
|
||||
only += [:field7] unless channel.field7.blank? or (params[:field_id] and params[:field_id] != '7')
|
||||
only += [:field8] unless channel.field8.blank? or (params[:field_id] and params[:field_id] != '8')
|
||||
only += [:status] if params[:status] and params[:status].upcase == 'TRUE'
|
||||
|
||||
return only
|
||||
end
|
||||
|
||||
# checks for valid timescale
|
||||
def timeparam_valid?(timeparam)
|
||||
valid_minutes = [10, 15, 20, 30, 60, 240, 720, 1440]
|
||||
if timeparam and valid_minutes.include?(timeparam.to_i)
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
# slice feed into timescales
|
||||
def feeds_into_timescales(feeds)
|
||||
# convert timescale (minutes) into seconds
|
||||
seconds = params[:timescale].to_i * 60
|
||||
# get floored time ranges
|
||||
start_time = get_floored_time(feeds.first.created_at, seconds)
|
||||
end_time = get_floored_time(feeds.last.created_at, seconds)
|
||||
|
||||
# create empty array with appropriate size
|
||||
timeslices = Array.new(((end_time - start_time) / seconds).floor)
|
||||
|
||||
# create a blank clone of the first feed so that we only get the necessary attributes
|
||||
empty_feed = create_empty_clone(feeds.first)
|
||||
|
||||
# add feeds to array
|
||||
feeds.each do |f|
|
||||
i = ((f.created_at - start_time) / seconds).floor
|
||||
f.created_at = start_time + i * seconds
|
||||
timeslices[i] = f if timeslices[i].nil?
|
||||
end
|
||||
|
||||
# fill in empty array elements
|
||||
timeslices.each_index do |i|
|
||||
if timeslices[i].nil?
|
||||
current_feed = empty_feed.clone
|
||||
current_feed.created_at = (start_time + (i * seconds))
|
||||
timeslices[i] = current_feed
|
||||
end
|
||||
end
|
||||
|
||||
return timeslices
|
||||
end
|
||||
|
||||
# slice feed into averages
|
||||
def feeds_into_averages(feeds)
|
||||
# convert timescale (minutes) into seconds
|
||||
seconds = params[:average].to_i * 60
|
||||
# get floored time ranges
|
||||
start_time = get_floored_time(feeds.first.created_at, seconds)
|
||||
end_time = get_floored_time(feeds.last.created_at, seconds)
|
||||
|
||||
# create empty array with appropriate size
|
||||
timeslices = Array.new(((end_time - start_time) / seconds).floor)
|
||||
|
||||
# create a blank clone of the first feed so that we only get the necessary attributes
|
||||
empty_feed = create_empty_clone(feeds.first)
|
||||
|
||||
# add feeds to array
|
||||
feeds.each do |f|
|
||||
i = ((f.created_at - start_time) / seconds).floor
|
||||
f.created_at = start_time + i * seconds
|
||||
# create multidimensional array
|
||||
timeslices[i] = [] if timeslices[i].nil?
|
||||
timeslices[i].push(f)
|
||||
end
|
||||
|
||||
# keep track of whether numbers use commas as decimals
|
||||
comma_flag = false
|
||||
|
||||
# fill in array
|
||||
timeslices.each_index do |i|
|
||||
# insert empty values
|
||||
if timeslices[i].nil?
|
||||
current_feed = empty_feed.clone
|
||||
current_feed.created_at = (start_time + (i * seconds))
|
||||
timeslices[i] = current_feed
|
||||
# else average the inner array
|
||||
else
|
||||
sum_feed = empty_feed.clone
|
||||
sum_feed.created_at = timeslices[i].first.created_at
|
||||
# for each feed
|
||||
timeslices[i].each do |f|
|
||||
# for each attribute, add to sum_feed so that we have the total
|
||||
sum_feed.attribute_names.each do |attr|
|
||||
|
||||
# only add non-null integer fields
|
||||
if attr.index('field') and !f[attr].nil? and is_a_number?(f[attr])
|
||||
# set comma_flag once if we find a number with a comma
|
||||
comma_flag = true if !comma_flag and f[attr].to_s.index(',')
|
||||
|
||||
# set initial data
|
||||
if sum_feed[attr].nil?
|
||||
sum_feed[attr] = parsefloat(f[attr])
|
||||
# add data
|
||||
elsif f[attr]
|
||||
sum_feed[attr] = parsefloat(sum_feed[attr]) + parsefloat(f[attr])
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
# set to the averaged feed
|
||||
timeslices[i] = object_average(sum_feed, timeslices[i].length, comma_flag, params[:round])
|
||||
end
|
||||
end
|
||||
|
||||
return timeslices
|
||||
end
|
||||
|
||||
# slice feed into medians
|
||||
def feeds_into_medians(feeds)
|
||||
# convert timescale (minutes) into seconds
|
||||
seconds = params[:median].to_i * 60
|
||||
# get floored time ranges
|
||||
start_time = get_floored_time(feeds.first.created_at, seconds)
|
||||
end_time = get_floored_time(feeds.last.created_at, seconds)
|
||||
|
||||
# create empty array with appropriate size
|
||||
timeslices = Array.new(((end_time - start_time) / seconds).floor)
|
||||
|
||||
# create a blank clone of the first feed so that we only get the necessary attributes
|
||||
empty_feed = create_empty_clone(feeds.first)
|
||||
|
||||
# add feeds to array
|
||||
feeds.each do |f|
|
||||
i = ((f.created_at - start_time) / seconds).floor
|
||||
f.created_at = start_time + i * seconds
|
||||
# create multidimensional array
|
||||
timeslices[i] = [] if timeslices[i].nil?
|
||||
timeslices[i].push(f)
|
||||
end
|
||||
|
||||
# keep track of whether numbers use commas as decimals
|
||||
comma_flag = false
|
||||
|
||||
# fill in array
|
||||
timeslices.each_index do |i|
|
||||
# insert empty values
|
||||
if timeslices[i].nil?
|
||||
current_feed = empty_feed.clone
|
||||
current_feed.created_at = (start_time + (i * seconds))
|
||||
timeslices[i] = current_feed
|
||||
# else get median values for the inner array
|
||||
else
|
||||
|
||||
# create blank hash called 'fields' to hold data
|
||||
fields = {}
|
||||
|
||||
# for each feed
|
||||
timeslices[i].each do |f|
|
||||
|
||||
# for each attribute
|
||||
f.attribute_names.each do |attr|
|
||||
if attr.index('field')
|
||||
|
||||
# create blank array for each field
|
||||
fields["#{attr}"] = [] if fields["#{attr}"].nil?
|
||||
|
||||
# push numeric field data onto its array
|
||||
if is_a_number?(f[attr])
|
||||
# set comma_flag once if we find a number with a comma
|
||||
comma_flag = true if !comma_flag and f[attr].to_s.index(',')
|
||||
|
||||
fields["#{attr}"].push(parsefloat(f[attr]))
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# sort fields arrays
|
||||
fields.each_key do |key|
|
||||
fields[key] = fields[key].compact.sort
|
||||
end
|
||||
|
||||
# get the median
|
||||
median_feed = empty_feed.clone
|
||||
median_feed.created_at = timeslices[i].first.created_at
|
||||
median_feed.attribute_names.each do |attr|
|
||||
median_feed[attr] = object_median(fields[attr], comma_flag, params[:round]) if attr.index('field')
|
||||
end
|
||||
|
||||
timeslices[i] = median_feed
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
return timeslices
|
||||
end
|
||||
|
||||
# slice feed into sums
|
||||
def feeds_into_sums(feeds)
|
||||
# convert timescale (minutes) into seconds
|
||||
seconds = params[:sum].to_i * 60
|
||||
# get floored time ranges
|
||||
start_time = get_floored_time(feeds.first.created_at, seconds)
|
||||
end_time = get_floored_time(feeds.last.created_at, seconds)
|
||||
|
||||
# create empty array with appropriate size
|
||||
timeslices = Array.new(((end_time - start_time) / seconds).floor)
|
||||
|
||||
# create a blank clone of the first feed so that we only get the necessary attributes
|
||||
empty_feed = create_empty_clone(feeds.first)
|
||||
|
||||
# add feeds to array
|
||||
feeds.each do |f|
|
||||
i = ((f.created_at - start_time) / seconds).floor
|
||||
f.created_at = start_time + i * seconds
|
||||
# create multidimensional array
|
||||
timeslices[i] = [] if timeslices[i].nil?
|
||||
timeslices[i].push(f)
|
||||
end
|
||||
|
||||
# keep track of whether numbers use commas as decimals
|
||||
comma_flag = false
|
||||
|
||||
# fill in array
|
||||
timeslices.each_index do |i|
|
||||
# insert empty values
|
||||
if timeslices[i].nil?
|
||||
current_feed = empty_feed.clone
|
||||
current_feed.created_at = (start_time + (i * seconds))
|
||||
timeslices[i] = current_feed
|
||||
# else sum the inner array
|
||||
else
|
||||
sum_feed = empty_feed.clone
|
||||
sum_feed.created_at = timeslices[i].first.created_at
|
||||
# for each feed
|
||||
timeslices[i].each do |f|
|
||||
# for each attribute, add to sum_feed so that we have the total
|
||||
sum_feed.attribute_names.each do |attr|
|
||||
# only add non-null integer fields
|
||||
if attr.index('field') and !f[attr].nil? and is_a_number?(f[attr])
|
||||
|
||||
# set comma_flag once if we find a number with a comma
|
||||
comma_flag = true if !comma_flag and f[attr].to_s.index(',')
|
||||
|
||||
# set initial data
|
||||
if sum_feed[attr].nil?
|
||||
sum_feed[attr] = parsefloat(f[attr])
|
||||
# add data
|
||||
elsif f[attr]
|
||||
sum_feed[attr] = parsefloat(sum_feed[attr]) + parsefloat(f[attr])
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# set to the summed feed
|
||||
timeslices[i] = object_sum(sum_feed, comma_flag, params[:round])
|
||||
end
|
||||
end
|
||||
|
||||
return timeslices
|
||||
end
|
||||
|
||||
def is_a_number?(s)
|
||||
s.to_s.gsub(/,/, '.').match(/\A[+-]?\d+?(\.\d+)?\Z/) == nil ? false : true
|
||||
end
|
||||
|
||||
def parsefloat(number)
|
||||
return number.to_s.gsub(/,/, '.').to_f
|
||||
end
|
||||
|
||||
# gets the median for an object
|
||||
def object_median(object, comma_flag=false, round=nil)
|
||||
return nil if object.nil?
|
||||
length = object.length
|
||||
return nil if length == 0
|
||||
output = ''
|
||||
|
||||
# do the calculation
|
||||
if length % 2 == 0
|
||||
output = (object[(length - 1) / 2] + object[length / 2]) / 2
|
||||
else
|
||||
output = object[(length - 1) / 2]
|
||||
end
|
||||
|
||||
output = sprintf "%.#{round}f", output if round and is_a_number?(output)
|
||||
|
||||
# replace decimals with commas if appropriate
|
||||
output = output.to_s.gsub(/\./, ',') if comma_flag
|
||||
|
||||
return output.to_s
|
||||
end
|
||||
|
||||
# averages a summed object over length
|
||||
def object_average(object, length, comma_flag=false, round=nil)
|
||||
object.attribute_names.each do |attr|
|
||||
# only average non-null integer fields
|
||||
if !object[attr].nil? and is_a_number?(object[attr])
|
||||
if round
|
||||
object[attr] = sprintf "%.#{round}f", (parsefloat(object[attr]) / length)
|
||||
else
|
||||
object[attr] = (parsefloat(object[attr]) / length).to_s
|
||||
end
|
||||
# replace decimals with commas if appropriate
|
||||
object[attr] = object[attr].gsub(/\./, ',') if comma_flag
|
||||
end
|
||||
end
|
||||
|
||||
return object
|
||||
end
|
||||
|
||||
# formats a summed object correctly
|
||||
def object_sum(object, comma_flag=false, round=nil)
|
||||
object.attribute_names.each do |attr|
|
||||
# only average non-null integer fields
|
||||
if !object[attr].nil? and is_a_number?(object[attr])
|
||||
if round
|
||||
object[attr] = sprintf "%.#{round}f", parsefloat(object[attr])
|
||||
else
|
||||
object[attr] = parsefloat(object[attr]).to_s
|
||||
end
|
||||
# replace decimals with commas if appropriate
|
||||
object[attr] = object[attr].gsub(/\./, ',') if comma_flag
|
||||
end
|
||||
end
|
||||
|
||||
return object
|
||||
end
|
||||
|
||||
# creates an empty clone of an object
|
||||
def create_empty_clone(object)
|
||||
empty_clone = object.clone
|
||||
empty_clone.attribute_names.each { |attr| empty_clone[attr] = nil }
|
||||
return empty_clone
|
||||
end
|
||||
|
||||
# gets time floored to proper interval
|
||||
def get_floored_time(input_time, seconds)
|
||||
return Time.zone.at((input_time.to_f / seconds).floor * seconds)
|
||||
end
|
||||
|
||||
end
|
23
app/controllers/mailer_controller.rb
Normal file
23
app/controllers/mailer_controller.rb
Normal file
@ -0,0 +1,23 @@
|
||||
class MailerController < ApplicationController
|
||||
|
||||
def resetpassword
|
||||
# protect against bots
|
||||
render :text => '' and return if params[:userlogin].length > 0
|
||||
|
||||
@user = User.find_by_login_or_email(params[:user][:login])
|
||||
if @user.nil?
|
||||
sleep 2
|
||||
session[:mail_message] = t(:account_not_found)
|
||||
else
|
||||
begin
|
||||
@user.reset_perishable_token!
|
||||
#Mailer.password_reset(@user, "https://www.thingspeak.com/users/reset_password/#{@user.id}?token=#{@user.perishable_token}").deliver
|
||||
session[:mail_message] = t(:password_reset_mailed)
|
||||
rescue
|
||||
session[:mail_message] = t(:password_reset_error)
|
||||
end
|
||||
end
|
||||
redirect_to :controller => 'user_session', :action => 'new'
|
||||
end
|
||||
|
||||
end
|
7
app/controllers/pages_controller.rb
Normal file
7
app/controllers/pages_controller.rb
Normal file
@ -0,0 +1,7 @@
|
||||
class PagesController < ApplicationController
|
||||
|
||||
def home
|
||||
@menu = 'home'
|
||||
end
|
||||
|
||||
end
|
113
app/controllers/status_controller.rb
Normal file
113
app/controllers/status_controller.rb
Normal file
@ -0,0 +1,113 @@
|
||||
class StatusController < ApplicationController
|
||||
require 'csv'
|
||||
|
||||
def index
|
||||
@channel = Channel.find(params[:channel_id])
|
||||
@api_key = ApiKey.find_by_api_key(get_userkey)
|
||||
@success = channel_permission?(@channel, @api_key)
|
||||
|
||||
# check for access
|
||||
if @success
|
||||
# create options hash
|
||||
channel_options = { :only => channel_select_terse(@channel) }
|
||||
|
||||
# display only 1 day by default
|
||||
params[:days] = 1 if !params[:days]
|
||||
|
||||
# get feed based on conditions
|
||||
@feeds = Feed.find(
|
||||
:all,
|
||||
:conditions => { :channel_id => @channel.id, :created_at => get_date_range(params) },
|
||||
:select => [:created_at, :status],
|
||||
:order => 'created_at'
|
||||
)
|
||||
|
||||
# set output correctly
|
||||
if params[:format] == 'xml'
|
||||
@channel_xml = @channel.to_xml(channel_options).sub('</channel>', '').strip
|
||||
@feed_xml = @feeds.to_xml(:skip_instruct => true).gsub(/\n/, "\n ").chop.chop
|
||||
elsif params[:format] == 'csv'
|
||||
@csv_headers = [:created_at, :status]
|
||||
else
|
||||
@channel_json = @channel.to_json(channel_options).chop
|
||||
@feed_json = @feeds.to_json
|
||||
end
|
||||
# else set error code
|
||||
else
|
||||
if params[:format] == 'xml'
|
||||
@channel_xml = bad_channel_xml
|
||||
else
|
||||
@channel_json = '-1'.to_json
|
||||
end
|
||||
end
|
||||
|
||||
# set callback for jsonp
|
||||
@callback = params[:callback] if params[:callback]
|
||||
|
||||
# output data in proper format
|
||||
respond_to do |format|
|
||||
format.html { render :text => @feed_json }
|
||||
format.json { render :action => 'feed/index' }
|
||||
format.xml { render :action => 'feed/index' }
|
||||
format.csv { render :action => 'feed/index' }
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@channel = Channel.find(params[:channel_id])
|
||||
@api_key = ApiKey.find_by_api_key(params[:key])
|
||||
output = '-1'
|
||||
|
||||
# get most recent entry if necessary
|
||||
params[:id] = @channel.last_entry_id if params[:id] == 'last'
|
||||
|
||||
@feed = Feed.find(
|
||||
:first,
|
||||
:conditions => { :channel_id => @channel.id, :entry_id => params[:id] },
|
||||
:select => [:created_at, :status]
|
||||
)
|
||||
@success = channel_permission?(@channel, @api_key)
|
||||
|
||||
# check for access
|
||||
if @success
|
||||
# set output correctly
|
||||
if params[:format] == 'xml'
|
||||
output = @feed.to_xml
|
||||
elsif params[:format] == 'csv'
|
||||
@csv_headers = [:created_at, :entry_id, :status]
|
||||
elsif (params[:format] == 'txt' or params[:format] == 'text')
|
||||
output = add_prepend_append(@feed.status)
|
||||
else
|
||||
output = @feed.to_json
|
||||
end
|
||||
# else set error code
|
||||
else
|
||||
if params[:format] == 'xml'
|
||||
output = bad_feed_xml
|
||||
else
|
||||
output = '-1'.to_json
|
||||
end
|
||||
end
|
||||
|
||||
# output data in proper format
|
||||
respond_to do |format|
|
||||
format.html { render :json => output }
|
||||
format.json { render :json => output, :callback => params[:callback] }
|
||||
format.xml { render :xml => output }
|
||||
format.csv { render :action => 'feed/show' }
|
||||
format.text { render :text => output }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# only output these fields for channel
|
||||
def channel_select_terse(channel)
|
||||
only = [:name]
|
||||
only += [:latitude] unless channel.latitude.nil?
|
||||
only += [:longitude] unless channel.longitude.nil?
|
||||
only += [:elevation] unless channel.elevation.nil? or channel.elevation.empty?
|
||||
|
||||
return only
|
||||
end
|
||||
|
||||
end
|
15
app/controllers/subdomains_controller.rb
Normal file
15
app/controllers/subdomains_controller.rb
Normal file
@ -0,0 +1,15 @@
|
||||
class SubdomainsController < ApplicationController
|
||||
|
||||
# show a blank page if subdomain
|
||||
def index
|
||||
render :text => ''
|
||||
end
|
||||
|
||||
# output the file crossdomain.xml.erb
|
||||
def crossdomain
|
||||
respond_to do |format|
|
||||
format.xml
|
||||
end
|
||||
end
|
||||
|
||||
end
|
44
app/controllers/user_sessions_controller.rb
Normal file
44
app/controllers/user_sessions_controller.rb
Normal file
@ -0,0 +1,44 @@
|
||||
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
|
||||
redirect_to root_path and return
|
||||
else
|
||||
# prevent timing and brute force password attacks
|
||||
sleep 1
|
||||
@failed = true
|
||||
render :action => :new
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
current_user_session.destroy
|
||||
reset_session
|
||||
redirect_to root_path
|
||||
end
|
||||
end
|
92
app/controllers/users_controller.rb
Normal file
92
app/controllers/users_controller.rb
Normal file
@ -0,0 +1,92 @@
|
||||
class UsersController < ApplicationController
|
||||
before_filter :require_no_user, :only => [:new, :create, :forgot_password]
|
||||
before_filter :require_user, :only => [:show, :edit, :update, :change_password]
|
||||
|
||||
def new
|
||||
@title = t(:signup)
|
||||
@user = User.new
|
||||
end
|
||||
|
||||
def create
|
||||
# protect against bots
|
||||
render :text => '' and return if params[:userlogin].length > 0
|
||||
|
||||
# check for invite code
|
||||
render :text => 'Sorry, you currently need an invite code to sign up.' and return if params[:invite] != '4224'
|
||||
|
||||
@user = User.new(params[:user])
|
||||
|
||||
# save user
|
||||
if @user.valid?
|
||||
if @user.save
|
||||
redirect_back_or_default account_path and return
|
||||
end
|
||||
else
|
||||
render :action => :new
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def show
|
||||
@menu = 'account'
|
||||
@user = @current_user
|
||||
end
|
||||
|
||||
def edit
|
||||
@menu = 'account'
|
||||
@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_to_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
|
||||
# protect against bots
|
||||
render :text => '' and return if params[:userlogin].length > 0
|
||||
|
||||
@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(params[:user])
|
||||
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
|
||||
# check current password and update
|
||||
if @user.valid_password?(params[:password_current]) && @user.update_attributes(params[:user])
|
||||
redirect_to account_path
|
||||
else
|
||||
@user.errors.add_to_base(t(:password_incorrect))
|
||||
render :action => :edit
|
||||
end
|
||||
end
|
||||
|
||||
end
|
2
app/helpers/api_keys_helper.rb
Normal file
2
app/helpers/api_keys_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module ApiKeysHelper
|
||||
end
|
2
app/helpers/application_helper.rb
Normal file
2
app/helpers/application_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module ApplicationHelper
|
||||
end
|
2
app/helpers/charts_helper.rb
Normal file
2
app/helpers/charts_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module ChartsHelper
|
||||
end
|
2
app/helpers/feed_helper.rb
Normal file
2
app/helpers/feed_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module FeedHelper
|
||||
end
|
2
app/helpers/mailer_helper.rb
Normal file
2
app/helpers/mailer_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module MailerHelper
|
||||
end
|
2
app/helpers/pages_helper.rb
Normal file
2
app/helpers/pages_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module PagesHelper
|
||||
end
|
2
app/helpers/status_helper.rb
Normal file
2
app/helpers/status_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module StatusHelper
|
||||
end
|
2
app/helpers/subdomains_helper.rb
Normal file
2
app/helpers/subdomains_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module SubdomainsHelper
|
||||
end
|
2
app/helpers/users_helper.rb
Normal file
2
app/helpers/users_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module UsersHelper
|
||||
end
|
24
app/models/api_key.rb
Normal file
24
app/models/api_key.rb
Normal file
@ -0,0 +1,24 @@
|
||||
class ApiKey < ActiveRecord::Base
|
||||
belongs_to :channel
|
||||
|
||||
validates_uniqueness_of :api_key
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: api_keys
|
||||
#
|
||||
# id :integer(4) not null, primary key
|
||||
# api_key :string(16)
|
||||
# channel_id :integer(4)
|
||||
# user_id :integer(4)
|
||||
# write_flag :boolean(1) default(FALSE)
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# note :string(255)
|
||||
#
|
||||
|
44
app/models/channel.rb
Normal file
44
app/models/channel.rb
Normal file
@ -0,0 +1,44 @@
|
||||
class Channel < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
has_many :feeds
|
||||
has_many :api_keys
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: channels
|
||||
#
|
||||
# id :integer(4) not null, primary key
|
||||
# user_id :integer(4)
|
||||
# name :string(255)
|
||||
# description :string(255)
|
||||
# latitude :decimal(15, 10)
|
||||
# longitude :decimal(15, 10)
|
||||
# field1 :text
|
||||
# field2 :text
|
||||
# field3 :text
|
||||
# field4 :text
|
||||
# field5 :text
|
||||
# field6 :text
|
||||
# field7 :text
|
||||
# field8 :text
|
||||
# scale1 :integer(4)
|
||||
# scale2 :integer(4)
|
||||
# scale3 :integer(4)
|
||||
# scale4 :integer(4)
|
||||
# scale5 :integer(4)
|
||||
# scale6 :integer(4)
|
||||
# scale7 :integer(4)
|
||||
# scale8 :integer(4)
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# elevation :string(255)
|
||||
# last_entry_id :integer(4)
|
||||
# public_flag :boolean(1) default(FALSE)
|
||||
#
|
||||
|
30
app/models/feed.rb
Normal file
30
app/models/feed.rb
Normal file
@ -0,0 +1,30 @@
|
||||
class Feed < ActiveRecord::Base
|
||||
belongs_to :channel
|
||||
|
||||
self.include_root_in_json = false
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: feeds
|
||||
#
|
||||
# id :integer(4) not null, primary key
|
||||
# channel_id :integer(4)
|
||||
# raw_data :text
|
||||
# field1 :text
|
||||
# field2 :text
|
||||
# field3 :text
|
||||
# field4 :text
|
||||
# field5 :text
|
||||
# field6 :text
|
||||
# field7 :text
|
||||
# field8 :text
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# entry_id :integer(4)
|
||||
#
|
||||
|
11
app/models/mailer.rb
Normal file
11
app/models/mailer.rb
Normal file
@ -0,0 +1,11 @@
|
||||
class Mailer < ActionMailer::Base
|
||||
#default :from => 'support@thingspeak.com'
|
||||
|
||||
def password_reset(user, webpage)
|
||||
@user = user
|
||||
@webpage = webpage
|
||||
mail(:to => @user.email,
|
||||
:subject => t(:password_reset_subject))
|
||||
end
|
||||
|
||||
end
|
31
app/models/user.rb
Normal file
31
app/models/user.rb
Normal file
@ -0,0 +1,31 @@
|
||||
class User < ActiveRecord::Base
|
||||
has_many :channels
|
||||
|
||||
acts_as_authentic
|
||||
|
||||
def self.find_by_login_or_email(login)
|
||||
User.find_by_login(login) || User.find_by_email(login)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: users
|
||||
#
|
||||
# id :integer(4) 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)
|
||||
#
|
||||
|
7
app/models/user_session.rb
Normal file
7
app/models/user_session.rb
Normal file
@ -0,0 +1,7 @@
|
||||
class UserSession < Authlogic::Session::Base
|
||||
find_by_login_method :find_by_login_or_email
|
||||
|
||||
def to_key
|
||||
new_record? ? nil : [ self.send(self.class.primary_key) ]
|
||||
end
|
||||
end
|
35
app/views/api_keys/index.html.erb
Normal file
35
app/views/api_keys/index.html.erb
Normal file
@ -0,0 +1,35 @@
|
||||
<h2><%= t(:api_key_write) %></h2>
|
||||
<%= button_to t(:api_key_write_new), channel_api_keys_path(@channel, :write => 1), :confirm => t(:confirm_new_api_key) %>
|
||||
<br />
|
||||
<%= @key %>
|
||||
|
||||
<br /><br />
|
||||
|
||||
<h2><%= t(:api_key_read) %></h2>
|
||||
<%= button_to t(:api_key_read_new), channel_api_keys_path(@channel, :write => 0), %>
|
||||
<br />
|
||||
|
||||
<% @read_keys.each do |read_key| %>
|
||||
<table>
|
||||
<tr>
|
||||
<td><%= t(:api_key_key) %>:</td>
|
||||
<td><%= read_key.api_key %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="VAT"><%= t(:note) %>:</td>
|
||||
<td>
|
||||
<%= form_for read_key, :as => :api_key, :url => { :controller => 'api_keys', :action => 'update' }, :html => {:method => 'put'} do |f| %>
|
||||
<%= f.text_area :note, :cols => 30, :rows => 4 %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= f.hidden_field :api_key, :value => read_key.api_key %></td>
|
||||
<td>
|
||||
<div class="FL"><%= f.submit t(:note_save) %></div>
|
||||
<% end %>
|
||||
<%= button_to t(:api_key_delete), :controller => 'api_keys', :action => 'destroy', :api_key => read_key.api_key %></td>
|
||||
</tr>
|
||||
</table>
|
||||
<br /><br />
|
||||
|
||||
<% end %>
|
112
app/views/channels/edit.html.erb
Normal file
112
app/views/channels/edit.html.erb
Normal file
@ -0,0 +1,112 @@
|
||||
<h2><%= t(:channel_edit) %></h2>
|
||||
|
||||
<%= form_for @channel, :html => {:method => 'put'} do |d| %>
|
||||
<%= error_messages_for 'channel', :header_message => t(:try_again), :message => t(:channel_error) %>
|
||||
<input name='userlogin' class='userlogin' />
|
||||
<table>
|
||||
<tr>
|
||||
<td><%= t(:channel_name) %></td>
|
||||
<td><%= d.text_field :name %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:channel_description) %></td>
|
||||
<td><%= d.text_field :description %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:api_key) %></td>
|
||||
<td><%= @key %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:latitude) %></td>
|
||||
<td><%= d.text_field :latitude %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:longitude) %></td>
|
||||
<td><%= d.text_field :longitude %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:elevation) %></td>
|
||||
<td><%= d.text_field :elevation %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:public) %></td>
|
||||
<td><%= d.check_box :public_flag %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:field) %> 1</td>
|
||||
<td><%= d.text_field :field1, :class => 'field' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:field) %> 2</td>
|
||||
<td><%= d.text_field :field2, :class => 'field' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:field) %> 3</td>
|
||||
<td><%= d.text_field :field3, :class => 'field' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:field) %> 4</td>
|
||||
<td><%= d.text_field :field4, :class => 'field' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:field) %> 5</td>
|
||||
<td><%= d.text_field :field5, :class => 'field' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:field) %> 6</td>
|
||||
<td><%= d.text_field :field6, :class => 'field' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:field) %> 7</td>
|
||||
<td><%= d.text_field :field7, :class => 'field' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:field) %> 8</td>
|
||||
<td><%= d.text_field :field8, :class => 'field' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><%= d.submit t(:channel_update) %>
|
||||
</tr>
|
||||
</table>
|
||||
<% end %>
|
||||
|
||||
<br /><br />
|
||||
|
||||
<h2><%= t(:channel_delete_message) %></h2>
|
||||
<%= button_to t(:channel_delete), channel_path(@channel.id), :method => 'delete', :confirm => t(:confirm_channel_delete) %>
|
||||
|
||||
<script type="text/javascript">
|
||||
// remember default field label
|
||||
var default_label = '<%= t(:channel_default_field) %>';
|
||||
// when document is ready
|
||||
$(function() {
|
||||
|
||||
// iterate through each field textbox
|
||||
$('.field').each(function() {
|
||||
// if a value is present, show the 'remove' checkbox
|
||||
if ($(this).val()) {
|
||||
$(this).after('<span class="small" id="span_' + $(this).attr('id') + '"><input type="checkbox" onclick="removeField(\'' + $(this).attr('id') + '\')" /><span class="up2">remove field</span></span>');
|
||||
// else disable
|
||||
} else {
|
||||
$(this).after('<span class="small" id="span_' + $(this).attr('id') + '"><input type="checkbox" onclick="addField(\'' + $(this).attr('id') + '\')" /><span class="up2">add field</span></span>');
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// adds a field
|
||||
function addField(id) {
|
||||
$('#span_'+id).remove()
|
||||
$('#'+id).val(default_label + ' ' + id.substring(id.length-1));
|
||||
$('#'+id).after('<span class="small" id="span_' + id + '"><input type="checkbox" onclick="removeField(\'' + id + '\')" /><span class="up2">remove field</span></span>');
|
||||
$('#'+id).select();
|
||||
}
|
||||
|
||||
// removes a field
|
||||
function removeField(id) {
|
||||
$('#span_'+id).remove();
|
||||
$('#'+id).val('');
|
||||
$('#'+id).after('<span class="small" id="span_' + id + '"><input type="checkbox" onclick="addField(\'' + id + '\')" /><span class="up2">add field</span></span>');
|
||||
}
|
||||
</script>
|
9
app/views/channels/index.html.erb
Normal file
9
app/views/channels/index.html.erb
Normal file
@ -0,0 +1,9 @@
|
||||
<%= form_for :channel do |d| %>
|
||||
<input name='userlogin' class='userlogin' />
|
||||
<%= d.submit t(:channel_create) %>
|
||||
<% end %>
|
||||
<br />
|
||||
<% @channels.each do |d| %>
|
||||
name: <%= link_to d.name, channel_path(d.id) %>
|
||||
<br />
|
||||
<% end %>
|
81
app/views/channels/show.html.erb
Normal file
81
app/views/channels/show.html.erb
Normal file
@ -0,0 +1,81 @@
|
||||
<% if current_user %>
|
||||
<%= link_to t(:channel_edit), edit_channel_path(@channel.id) %>
|
||||
<br />
|
||||
<%= link_to t(:api_keys_manage), channel_api_keys_path(@channel) %>
|
||||
<br />
|
||||
<%= link_to "#{t(:channel_feed)} (json)", channel_feed_index_path(@channel, :key => @key, :format => :json) %>
|
||||
<br />
|
||||
<%= link_to "#{t(:channel_feed)} (xml)", channel_feed_index_path(@channel, :key => @key, :format => :xml) %>
|
||||
<br />
|
||||
<%= link_to "#{t(:channel_feed)} (csv)", channel_feed_index_path(@channel, :key => @key, :format => :csv) %>
|
||||
<table>
|
||||
<tr>
|
||||
<td><%= t(:channel_name) %>:</td>
|
||||
<td><%= @channel.name %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:api_key) %>:</td>
|
||||
<td><%= @key %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:channel_description) %>:</td>
|
||||
<td><%= @channel.description %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:created) %>:</td>
|
||||
<td><%= l @channel.created_at, :format => :pretty %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:latitude) %>:</td>
|
||||
<td><%= @channel.latitude %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:longitude) %>:</td>
|
||||
<td><%= @channel.longitude %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:elevation) %>:</td>
|
||||
<td><%= @channel.elevation %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:field) %> 1:</td>
|
||||
<td><%= @channel.field1 %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:field) %> 2:</td>
|
||||
<td><%= @channel.field2 %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:field) %> 3:</td>
|
||||
<td><%= @channel.field3 %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:field) %> 4:</td>
|
||||
<td><%= @channel.field4 %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:field) %> 5:</td>
|
||||
<td><%= @channel.field5 %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:field) %> 6:</td>
|
||||
<td><%= @channel.field6 %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:field) %> 7:</td>
|
||||
<td><%= @channel.field7 %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:field) %> 8:</td>
|
||||
<td><%= @channel.field8 %></td>
|
||||
</tr>
|
||||
</table>
|
||||
<% else %>
|
||||
<% if @channel.public_flag %>
|
||||
<%= t(:channel_public) %>
|
||||
<br />
|
||||
<%= @channel.name %>
|
||||
<% else %>
|
||||
<%= t(:channel_not_public) %>
|
||||
<% end %>
|
||||
<% end %>
|
126
app/views/charts/_config.html.erb
Normal file
126
app/views/charts/_config.html.erb
Normal file
@ -0,0 +1,126 @@
|
||||
<% options = '×cale=10' if options.blank? %>
|
||||
<div>
|
||||
<h3><%= title %></h3>
|
||||
<table class="FL MR60">
|
||||
<tr>
|
||||
<td><%= t(:title) %>:</td>
|
||||
<td><input type="text" class="chart_options<%= index %>" id="title<%= index %>" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:chart_xaxis) %>:</td>
|
||||
<td><input type="text" class="chart_options<%= index %>" id="xaxis<%= index %>" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:chart_yaxis) %>:</td>
|
||||
<td><input type="text" class="chart_options<%= index %>" id="yaxis<%= index %>" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:chart_color) %>:</td>
|
||||
<td><input type="text" class="chart_options<%= index %>" id="color<%= index %>" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:chart_background_color) %>:</td>
|
||||
<td><input type="text" class="chart_options<%= index %>" id="bgcolor<%= index %>" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:chart_type) %>:</td>
|
||||
<td>
|
||||
<select class="chart_options<%= index %>" id="type<%= index %>">
|
||||
<option>line</option>
|
||||
<option>bar</option>
|
||||
<option>column</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><input type="button" id="button<%= index %>" value="<%= t(:chart_update) %>" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><%= t(:days) %>:</td>
|
||||
<td><input type="text" class="chart_options<%= index %> shortfield" id="days<%= index %>" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:timescale) %>:</td>
|
||||
<td><input type="text" class="chart_options<%= index %> shortfield" id="timescale<%= index %>" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:average) %>:</td>
|
||||
<td><input type="text" class="chart_options<%= index %> shortfield" id="average<%= index %>" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:median) %>:</td>
|
||||
<td><input type="text" class="chart_options<%= index %> shortfield" id="median<%= index %>" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:sum) %>:</td>
|
||||
<td><input type="text" class="chart_options<%= index %> shortfield" id="sum<%= index %>" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:chart_round) %>:</td>
|
||||
<td><input type="text" class="chart_options<%= index %> shortfield" id="round<%= index %>" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:width) %>:</td>
|
||||
<td><input type="text" class="chart_options<%= index %> shortfield" id="width<%= index %>" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:height) %>:</td>
|
||||
<td><input type="text" class="chart_options<%= index %> shortfield" id="height<%= index %>" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br class="CL" />
|
||||
|
||||
<iframe id="iframe<%= index %>" width="<%= width %>" height="<%= height %>" style="border: 1px solid #cccccc;" src="" default_src="<%= src %>"></iframe>
|
||||
|
||||
<br /><br />
|
||||
|
||||
<%= t(:chart_embed_code) %>:
|
||||
<br />
|
||||
<textarea id="embed<%= index %>" rows="5" cols="53"><iframe width="<%= width %>" height="<%= height %>" style="border: 1px solid #cccccc;" src="<%= src %>"></iframe></textarea>
|
||||
</div>
|
||||
|
||||
<br /><br /><br />
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
$(document).ready(function() {
|
||||
// set initial saved values
|
||||
$.each(('<%= options.gsub(/'/, "%27") if options %>'.split('&')), function(index, value) {
|
||||
if (value.length > 0) {
|
||||
$('#' + value.split('=')[0] + '<%= index %>').val(decodeURIComponent(value.split('=')[1]));
|
||||
}
|
||||
});
|
||||
|
||||
// draw initial chart with saved options
|
||||
updateChart(<%= index %>, false);
|
||||
});
|
||||
|
||||
// event to capture unfocus of textbox
|
||||
$('.chart_options<%= index %>').blur(function() {
|
||||
// if value exists, update the chart
|
||||
if ($(this).val().length > 0) {
|
||||
updateChart(<%= index %>, true);
|
||||
}
|
||||
});
|
||||
|
||||
// event to capture enter key in textboxes
|
||||
$('.chart_options<%= index %>').keyup(function(e) {
|
||||
// if enter key
|
||||
if (e.keyCode == 13) {
|
||||
// if value exists, update the chart
|
||||
if ($(this).val().length > 0) {
|
||||
updateChart(<%= index %>, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// event to capture update button click
|
||||
$('#button<%= index %>').click(function() {
|
||||
updateChart(<%= index %>, true);
|
||||
});
|
||||
</script>
|
103
app/views/charts/index.html.erb
Normal file
103
app/views/charts/index.html.erb
Normal file
@ -0,0 +1,103 @@
|
||||
<%= javascript_include_tag 'rest' %>
|
||||
|
||||
<h2>
|
||||
<%= link_to t(:channels), channels_path %> »
|
||||
<%= link_to channel_path(@channel.id) do %> <%= t(:channel) %> <%= @channel.id %><% end %> »
|
||||
<%= t(:charts) %>
|
||||
</h2>
|
||||
|
||||
<%= render :partial => 'config',
|
||||
:locals => {
|
||||
:title => t(:chart_example),
|
||||
:src => "https://api.thingspeak.com/channels/3/charts/1",
|
||||
:options => '×cale=60&round=2',
|
||||
:index => 0,
|
||||
:width => @width,
|
||||
:height => @height
|
||||
}
|
||||
%>
|
||||
|
||||
<h3><%= t(:chart_owned) %></h3>
|
||||
|
||||
<% @channel.attribute_names.each do |attr| %>
|
||||
<% if attr.index('field') and @channel[attr] and !@channel[attr].empty? %>
|
||||
|
||||
<%= render :partial => 'config',
|
||||
:locals => {
|
||||
:title => "#{@channel.name} - #{@channel[attr]}",
|
||||
:src => "#{@domain}channels/#{@channel_id}/charts/#{attr[-1]}",
|
||||
:options => @channel["options#{attr[-1]}"],
|
||||
:index => attr[-1],
|
||||
:width => @width,
|
||||
:height => @height
|
||||
}
|
||||
%>
|
||||
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
$(document).ready(function() {
|
||||
// if chrome/safari error occurs, reload page
|
||||
if ($('#title0').val() == '60' && $('#color0').val() == '10') {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
// update the chart with all the textbox values
|
||||
function updateChart(index, postUpdate) {
|
||||
// default width and height
|
||||
var width = <%= @width %>;
|
||||
var height = <%= @height %>;
|
||||
// get old src
|
||||
var src = $('#iframe' + index).attr('default_src').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);
|
||||
}
|
||||
}
|
||||
|
||||
// add inputs to array
|
||||
var inputs = [];
|
||||
$('.chart_options' + index).each(function() {
|
||||
var v = $(this).val();
|
||||
var id = $(this).attr('id');
|
||||
if (v.length > 0) { inputs.push([id.substring(0, id.length-1), 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]); }
|
||||
|
||||
// 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) {
|
||||
$.update(
|
||||
'/channels/<%= @channel_id %>/charts/' + index,
|
||||
{ options: qs }
|
||||
);
|
||||
}
|
||||
|
||||
// set embed code
|
||||
$('#embed' + index).val('<iframe width="' + width + '" height="' + height + '" style="border: 1px solid #cccccc;" src="' + src + '"></iframe>');
|
||||
|
||||
// set new src
|
||||
$('#iframe' + index).attr('src', src);
|
||||
$('#iframe' + index).attr('width', width);
|
||||
$('#iframe' + index).attr('height', height);
|
||||
}
|
||||
|
||||
</script>
|
117
app/views/charts/show.html.erb
Normal file
117
app/views/charts/show.html.erb
Normal file
@ -0,0 +1,117 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="https://api.thingspeak.com/javascripts/highcharts<%= '-android' if get_header_value('user_agent').upcase.index('ANDROID') %>.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
$(document).ready(function() {
|
||||
// blank array for holding chart data
|
||||
var chartData = [];
|
||||
// variable for the date string
|
||||
var d;
|
||||
// variable for the data point
|
||||
var p;
|
||||
// variable for the local date in milliseconds
|
||||
var localDate;
|
||||
// users timezone offset
|
||||
var myOffset = new Date().getTimezoneOffset();
|
||||
|
||||
// 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) %>');
|
||||
}
|
||||
|
||||
// iterate through each feed
|
||||
$.each(data.feeds, function() {
|
||||
p = this.field<%= params[:id] %>;
|
||||
// if a numerical value exists add it
|
||||
if (!isNaN(parseInt(p))) {
|
||||
// get the date as a string
|
||||
d = this.created_at;
|
||||
|
||||
// add 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
|
||||
localDate = 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);
|
||||
chartData.push([localDate, parseFloat(p)]);
|
||||
}
|
||||
});
|
||||
|
||||
// specify the chart options
|
||||
var chartOptions = {
|
||||
chart: {
|
||||
renderTo: 'chart-container',
|
||||
defaultSeriesType: '<%= params[:type] ? "#{params[:type]}" : "line" %>',
|
||||
backgroundColor: '<%= params[:bgcolor] || "#ffffff" %>'
|
||||
},
|
||||
title: {
|
||||
text: ''
|
||||
},
|
||||
plotOptions: {
|
||||
line: {
|
||||
color: '<%= params[:color] || "#d62020" %>'
|
||||
},
|
||||
bar: {
|
||||
color: '<%= params[:color] || "#d62020" %>'
|
||||
},
|
||||
column: {
|
||||
color: '<%= params[:color] || "#d62020" %>'
|
||||
},
|
||||
series: {
|
||||
marker: {
|
||||
radius: 3
|
||||
},
|
||||
animation: false
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
// reformat the tooltips so that local times are displayed
|
||||
formatter: function() {
|
||||
var d = new Date(this.x + (myOffset*60000));
|
||||
return this.series.name + ':<b>' + this.y + '</b><br/>' + d.toDateString() + '<br/>' + d.toTimeString().replace(/\(.*\)/, "");
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: 'datetime',
|
||||
title: {
|
||||
text: ''
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
title: {
|
||||
text: ''
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
enabled: false
|
||||
},
|
||||
series: [{
|
||||
name: data.channel.field<%= params[:id] %>
|
||||
}]
|
||||
};
|
||||
|
||||
// add the data to the chart
|
||||
chartOptions.series[0].data = chartData;
|
||||
|
||||
// set chart labels here so that decoding occurs properly
|
||||
chartOptions.title.text = <% if params[:title] %>decodeURIComponent('<%= u(params[:title]) %>')<% else %>data.channel.name<% end %>;
|
||||
chartOptions.xAxis.title.text = <% if params[:xaxis] %>decodeURIComponent('<%= u(params[:xaxis]) %>')<% else %>'Date'<% end %>;
|
||||
chartOptions.yAxis.title.text = <% if params[:yaxis] %>decodeURIComponent('<%= u(params[:yaxis]) %>')<% else %><%= "data.channel.field#{params[:id]}" %><% end %>;
|
||||
|
||||
// draw the chart
|
||||
new Highcharts.Chart(chartOptions);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
</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>
|
||||
</body>
|
||||
</html>
|
1
app/views/feed/_index.json.erb
Normal file
1
app/views/feed/_index.json.erb
Normal file
@ -0,0 +1 @@
|
||||
<%= "#{@callback}(" if @callback %><%= raw(@channel_output) %><% if @success %>,"feeds":<%= raw(@feed_output) %>}<% end %><%= ')' if @callback %>
|
2
app/views/feed/index.csv.erb
Normal file
2
app/views/feed/index.csv.erb
Normal file
@ -0,0 +1,2 @@
|
||||
<% if @success %><%= CSV.generate_line @csv_headers %><% @feed_output.each do |feed| %><% row = [] %><% @csv_headers.each do |attr| %><% row.push(feed.send(attr)) %><% end %><%= CSV.generate_line row %><% end %><% else %>-1<% end %>
|
||||
|
1
app/views/feed/index.html.erb
Normal file
1
app/views/feed/index.html.erb
Normal file
@ -0,0 +1 @@
|
||||
<%= render :partial => 'index.json.erb' %>
|
1
app/views/feed/index.json.erb
Normal file
1
app/views/feed/index.json.erb
Normal file
@ -0,0 +1 @@
|
||||
<%= render :partial => 'feed/index.json.erb' %>
|
2
app/views/feed/index.xml.erb
Normal file
2
app/views/feed/index.xml.erb
Normal file
@ -0,0 +1,2 @@
|
||||
<%= raw(@channel_output) %><% if @success %>
|
||||
<%= raw(@feed_output) %></channel><% end %>
|
2
app/views/feed/show.csv.erb
Normal file
2
app/views/feed/show.csv.erb
Normal file
@ -0,0 +1,2 @@
|
||||
<% if @success %><%= CSV.generate_line @csv_headers %><% row = [] %><% @csv_headers.each do |attr| %><% row.push(@feed.send(attr)) %><% end %><%= CSV.generate_line row %><% else %>-1<% end %>
|
||||
|
12
app/views/layouts/_header.html.erb
Normal file
12
app/views/layouts/_header.html.erb
Normal file
@ -0,0 +1,12 @@
|
||||
<div id="options">
|
||||
<% if current_user %>
|
||||
<span class="action"> <%= link_to t(:signout), logout_path %></span>
|
||||
<% else %>
|
||||
<span class="action">
|
||||
<%= link_to t(:signup), new_user_path %>
|
||||
</span>
|
||||
<span class="action"> <%= link_to t(:signin), login_path %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div id="logo"><%= link_to t(:application_name), root_path %></div>
|
9
app/views/layouts/_menu.html.erb
Normal file
9
app/views/layouts/_menu.html.erb
Normal file
@ -0,0 +1,9 @@
|
||||
<% if @menu_type != 'nomenu' %>
|
||||
<div id="menu" class="round">
|
||||
<div<%= " class='selected'" if @menu == 'home' %>><%= link_to t(:home), root_path %></div>
|
||||
<% if current_user %>
|
||||
<div<%= " class='selected'" if @menu == 'account' %>><%= link_to t(:myaccount), account_path %></div>
|
||||
<div<%= " class='selected'" if @menu == 'channels' %>><%= link_to t(:channels), channels_path %></div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
17
app/views/layouts/application.html.erb
Normal file
17
app/views/layouts/application.html.erb
Normal file
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title><%= @title.nil? ? (@menu.nil? ? t(:application_name) : @menu.capitalize + ' - ' + t(:application_name)) : @title + ' - ' + t(:application_name) %></title>
|
||||
<%= stylesheet_link_tag :all %>
|
||||
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>
|
||||
<%= csrf_meta_tag %>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrapper">
|
||||
<div id="header"><div class="fixedwidth"><%= render 'layouts/header' %></div></div>
|
||||
<div id="menuwrap" class="fixedwidth"><%= render 'layouts/menu' %></div>
|
||||
<div id="content" class="fixedwidth"><%= yield %></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
17
app/views/mailer/password_reset.html.erb
Normal file
17
app/views/mailer/password_reset.html.erb
Normal file
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
<%= t(:password_reset_message1) %>
|
||||
<br />
|
||||
<%= t(:password_reset_message2) %>
|
||||
<br />
|
||||
<%= t(:password_reset_message3) %>
|
||||
<br /><br />
|
||||
<a href="<%= @webpage %>"><%= @webpage %></a>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
5
app/views/mailer/password_reset.txt.erb
Normal file
5
app/views/mailer/password_reset.txt.erb
Normal file
@ -0,0 +1,5 @@
|
||||
<%= t(:password_reset_message1) %>
|
||||
<%= t(:password_reset_message2) %>
|
||||
<%= t(:password_reset_message3) %>
|
||||
|
||||
<a href="<%= @webpage %>"><%= @webpage %></a>
|
7
app/views/pages/home.html.erb
Normal file
7
app/views/pages/home.html.erb
Normal file
@ -0,0 +1,7 @@
|
||||
<% if current_user %>
|
||||
<%= t(:homepage_logged_in) %>
|
||||
<% else %>
|
||||
|
||||
<%= t(:homepage) %>
|
||||
|
||||
<% end %>
|
1
app/views/pages/terms.html.erb
Normal file
1
app/views/pages/terms.html.erb
Normal file
@ -0,0 +1 @@
|
||||
<%= t(:tos) %>
|
7
app/views/subdomains/crossdomain.xml.erb
Normal file
7
app/views/subdomains/crossdomain.xml.erb
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
|
||||
<cross-domain-policy>
|
||||
<site-control permitted-cross-domain-policies="master-only"/>
|
||||
<allow-access-from domain="*"/>
|
||||
<allow-http-request-headers-from domain="*" headers="*"/>
|
||||
</cross-domain-policy>
|
13
app/views/user_sessions/new.html.erb
Normal file
13
app/views/user_sessions/new.html.erb
Normal file
@ -0,0 +1,13 @@
|
||||
<div class="FL"><%= render :partial => 'users/login' %></div>
|
||||
<div class="DT PL30">
|
||||
<% if @failed %>
|
||||
<h2 class="rubyred"><%= t(:signin_failure) %></h2>
|
||||
<%= t(:signin_try_again) %>
|
||||
<% else %>
|
||||
<div class="large">
|
||||
<%= t(:signin_please) %>
|
||||
</div>
|
||||
<br />
|
||||
<%= @mail_message %>
|
||||
<% end %>
|
||||
</div>
|
38
app/views/users/_login.html.erb
Normal file
38
app/views/users/_login.html.erb
Normal file
@ -0,0 +1,38 @@
|
||||
<%= form_for (@user_session = UserSession.new), :url => user_session_path, :html => { :id => 'loginform' } do |f| %>
|
||||
<input name='userlogin' class='userlogin' />
|
||||
<%= f.hidden_field :remember_me, :value => false %>
|
||||
<table id="login" class="round">
|
||||
<tr>
|
||||
<td colspan="2" class="text_center">
|
||||
<%= t(:secure_signin) %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="login_info"><%= t(:userid) %></td>
|
||||
<td><%= f.text_field :login, :size => 15, :value => cookies['user_id'] %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="login_info"><%= t(:password) %></td>
|
||||
<td><%= f.password_field :password, :size => 15 %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="login_info"><%= f.check_box :remember_id, :checked => true %></td>
|
||||
<td class="small"><%= t(:remember_me) %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><%= link_to t(:forgot), forgot_password_path, :id => 'forgot_password' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><%= f.submit t(:signin) %></td>
|
||||
</tr>
|
||||
</table>
|
||||
<% end %>
|
||||
<script type="text/javascript">
|
||||
var login = document.getElementById('user_session_login');
|
||||
if (login.value.length == 0)
|
||||
login.focus();
|
||||
else
|
||||
document.getElementById('user_session_password').focus();
|
||||
</script>
|
61
app/views/users/edit.html.erb
Normal file
61
app/views/users/edit.html.erb
Normal file
@ -0,0 +1,61 @@
|
||||
<h2><%= t(:account_edit) %></h2>
|
||||
<br />
|
||||
<%= form_for @user, :url => account_path do |f| %>
|
||||
<%= error_messages_for 'user', :header_message => t(:try_again), :message => t(:account_error_edit) %>
|
||||
<input name='userlogin' class='userlogin' />
|
||||
<table class="bigtable">
|
||||
<tr>
|
||||
<td class="left">
|
||||
<div class="vcenter"><%= f.label :login, t(:userid) %></div>
|
||||
</td>
|
||||
<td class="right">
|
||||
<%= f.text_field :login %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left">
|
||||
<div class="vcenter"><%= f.label t(:email) %></div>
|
||||
</td>
|
||||
<td class="right">
|
||||
<%= f.text_field :email %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:time_zone) %></td>
|
||||
<td><%= time_zone_select 'user', 'time_zone', nil, :default => 'Eastern Time (US & Canada)' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left">
|
||||
<div class="vcenter"><%= f.label :password, raw(t(:password_change_raw)) %></div>
|
||||
</td>
|
||||
<td class="right">
|
||||
<%= f.password_field :password %>
|
||||
</td>
|
||||
<tr>
|
||||
<td class="left">
|
||||
<%= f.label :password_confirmation, raw(t(:password_confirmation_raw)) %>
|
||||
</td>
|
||||
<td class="right">
|
||||
<%= f.password_field :password_confirmation %>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br /><br />
|
||||
<h3><%= t(:account_changes) %></h3>
|
||||
<table class="bigtable">
|
||||
<tr>
|
||||
<td class="left">
|
||||
<%= raw(t(:password_current_raw)) %>
|
||||
</td>
|
||||
<td class="right">
|
||||
<input name="password_current" type="password" />
|
||||
<br />
|
||||
<%= t(:account_security) %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left"></td>
|
||||
<td class="right"><%= f.submit t(:account_edit_submit) %></td>
|
||||
</tr>
|
||||
</table>
|
||||
<% end %>
|
11
app/views/users/forgot_password.html.erb
Normal file
11
app/views/users/forgot_password.html.erb
Normal file
@ -0,0 +1,11 @@
|
||||
<h2><%= t(:password_forgot) %></h2>
|
||||
<%= t(:password_forgot_message) %>
|
||||
<br /><br />
|
||||
<%= form_for @user, :url => { :controller => 'mailer', :action => 'resetpassword' } do |f| %>
|
||||
<input name='userlogin' class='userlogin' />
|
||||
<%= f.text_field :login %>
|
||||
<%= f.submit t(:submit) %>
|
||||
<% end %>
|
||||
<script type="text/javascript">
|
||||
document.getElementById('user_login').focus();
|
||||
</script>
|
65
app/views/users/new.html.erb
Normal file
65
app/views/users/new.html.erb
Normal file
@ -0,0 +1,65 @@
|
||||
<h2><%= t(:signup_header) %></h2>
|
||||
<br />
|
||||
<%= form_for @user, :url => account_path do |f| %>
|
||||
<%= error_messages_for 'user', :header_message => t(:try_again), :message => t(:account_error) %>
|
||||
<input name='userlogin' class='userlogin' />
|
||||
<table class="bigtable">
|
||||
<tr>
|
||||
<td class="left">
|
||||
<div class="vcenter"><%= f.label :login, t(:userid) %></div>
|
||||
</td>
|
||||
<td class="right">
|
||||
<%= f.text_field :login %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left">
|
||||
<div class="vcenter"><%= f.label t(:email) %></div>
|
||||
</td>
|
||||
<td class="right">
|
||||
<%= f.text_field :email %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= t(:time_zone) %></td>
|
||||
<td><%= time_zone_select 'user', 'time_zone', nil, :default => 'Eastern Time (US & Canada)' %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left">
|
||||
<div class="vcenter"><%= f.label t(:password) %></div>
|
||||
</td>
|
||||
<td class="right">
|
||||
<%= f.password_field :password %>
|
||||
</td>
|
||||
<tr>
|
||||
<td class="left">
|
||||
<%= f.label :password_confirmation, raw(t(:password_confirmation_raw)) %>
|
||||
</td>
|
||||
<td class="right">
|
||||
<%= f.password_field :password_confirmation %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left">
|
||||
Invite Code
|
||||
</td>
|
||||
<td class="right">
|
||||
<input name="invite" type="text" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left"></td>
|
||||
<td class="right">
|
||||
<%= t(:tos_agree) %> <%= link_to t(:tos), { :controller => 'pages', :action => 'terms' }, :target => '_blank' %>.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left"></td>
|
||||
<td class="right"><%= f.submit t(:create_account) %></td>
|
||||
</tr>
|
||||
</table>
|
||||
<% end %>
|
||||
|
||||
<script type="text/javascript">
|
||||
document.getElementById('user_login').focus();
|
||||
</script>
|
34
app/views/users/reset_password.html.erb
Normal file
34
app/views/users/reset_password.html.erb
Normal file
@ -0,0 +1,34 @@
|
||||
<% if @valid_link %>
|
||||
<h2><%= t(:password_new) %></h2>
|
||||
<%= form_for @user, :url => { :controller => 'users', :action => 'change_password', :id => @user.id } do |f| %>
|
||||
<%= error_messages_for 'user', :header_message => t(:try_again), :message => t(:password_new_error) %>
|
||||
<input name='userlogin' class='userlogin' />
|
||||
<table class="bigtable">
|
||||
<tr>
|
||||
<td class="left">
|
||||
<div class="vcenter"><%= f.label :password %></div>
|
||||
</td>
|
||||
<td class="right">
|
||||
<%= f.password_field :password %>
|
||||
<br />
|
||||
<%= t(:password_new_choose) %>
|
||||
</td>
|
||||
<tr>
|
||||
<td class="left">
|
||||
<%= f.label :password_confirmation, raw(t(:password_confirmation_raw)) %>
|
||||
</td>
|
||||
<td class="right">
|
||||
<%= f.password_field :password_confirmation %>
|
||||
<br />
|
||||
<%= t(:password_new_confirmation) %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left"></td>
|
||||
<td class="right"><%= f.submit t(:submit) %></td>
|
||||
</tr>
|
||||
</table>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= t(:password_link_expired) %>
|
||||
<% end %>
|
19
app/views/users/show.html.erb
Normal file
19
app/views/users/show.html.erb
Normal file
@ -0,0 +1,19 @@
|
||||
<h2><%= t(:account_info) %></h2>
|
||||
<table class="bigtable styletable">
|
||||
<tr>
|
||||
<td class="left"><%= t(:userid) %></td>
|
||||
<td><%= @user.login %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left"><%= t(:email) %></td>
|
||||
<td><%= @user.email %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="left"><%= t(:time_zone) %></td>
|
||||
<td><%= @user.time_zone %></td>
|
||||
</tr>
|
||||
</table>
|
||||
<br /><br />
|
||||
<div class="details">
|
||||
<%= link_to t(:account_edit), edit_account_path %>
|
||||
</div>
|
Reference in New Issue
Block a user