dt = self.finish_time - self.start_time
return nice_timedelta_str(dt)
+ def running_time(self):
+ dt = datetime.utcnow() - self.start_time
+ return nice_timedelta_str(dt)
+
def get_stats(self):
result = {}
try:
except:
return
# man 5 proc
+ # TODO:
+ # "getconf CLK_TCK" = 100 -> 1 tick = 1/100 seconds
strstats = strstats.rstrip('\n').split(' ')
for i, key in enumerate(('pid', 'comm', 'state', 'ppid', 'pgrp', 'session', 'tty_nr', 'tpgid', 'flags', 'minflt', 'cminflt', 'majflt', 'cmajflt', 'utime', 'stime', 'cutime', 'cstime', 'priority', 'nice', 'num_threads', 'itrealvalue', 'starttime', 'vsize', 'rss', 'rsslim', 'startcode', 'endcode', 'startstack', 'kstkesp', 'kstkeip', 'signal', 'blocked', 'sigignore', 'sigcatch', 'wchan', 'nswap', 'cnswap', 'exit_signal', 'processor', 'rt_priority', 'policy', 'delayacct_blkio_ticks', 'guest_time', 'cguest_time')):
result[key] = strstats[i]
# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/"
-MEDIA_ROOT = ''
+MEDIA_ROOT = '/usr/lib/ais/www/'
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash if there is a path component (optional in other cases).
# Examples: "http://media.lawrence.com", "http://example.com/media/"
-MEDIA_URL = ''
+MEDIA_URL = '/'
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash.
from django.template import loader, RequestContext
from django import forms
from django.shortcuts import render_to_response, get_object_or_404
+from django.utils.safestring import mark_safe
from decoratedstr import remove_decoration
from ais.inputs.stats import STATS_DIR
from ais.inputs.config import peers_get_config
from ais import jobrunner
+from ais.djais.widgets import *
def auth(username, raw_password):
try:
return render_to_response('vessel_index.html', {'form': form}, RequestContext(request))
+class PeriodWidget(forms.MultiWidget):
+ '''
+ A widget that splits period number and period type in a integer and a choice widgets
+ '''
+ __periods = (
+ (u'1', u'second(s)'),
+ (u'60', u'minute(s)'),
+ (u'3600', u'hour(s)'),
+ (u'86400', u'day(s)'),
+ (u'2592000', u'month(es)'))
+
+ def __init__(self, attrs=None):
+ textattrs = { 'size': 3 }
+ if attrs:
+ textattrs.update(attrs)
+ widgets = (
+ forms.TextInput(attrs=textattrs),
+ forms.Select(choices=(self.__periods), attrs=attrs))
+ super(PeriodWidget, self).__init__(widgets, attrs)
+
+ def decompress(self, value):
+ if value:
+ if value >= 2592000 and not value % 2592000:
+ return [ unicode(value // 2592000), u'2592000' ]
+ if value >= 86400 and not value % 86400:
+ return [ unicode(value // 86400), u'86400' ]
+ if value >= 3600 and not value % 3600:
+ return [ unicode(value // 3600), u'3600' ]
+ if value >= 60 and not value % 60:
+ return [ unicode(value // 60), u'60' ]
+ return [unicode(value), u'1']
+ return [None, None]
+
+class PeriodField(forms.MultiValueField):
+ widget = PeriodWidget
+
+ def __init__(self, *args, **kargs):
+ fields = (
+ forms.IntegerField(),
+ forms.CharField(),
+ )
+ forms.MultiValueField.__init__(self, fields=fields, *args, **kargs)
+
+ def compress(self, data_list):
+ #print 'data_list=', data_list
+ if data_list and data_list[0] is not None and data_list[1] is not None:
+ try:
+ return data_list[0] * int(data_list[1])
+ except ValueError:
+ pass
+ return None
+
+
+class HistoryForm(forms.Form):
+ format = forms.ChoiceField(choices=(('track', 'Track line (Google Earth)'), ('animation', 'Animation (Google Earth)'), ('csv', 'Coma separated values (SpreadSheet)')), widget=forms.Select(attrs={'onchange': mark_safe("if (this.value=='csv') $('#csvhint').show(); else $('#csvhint').hide();")}))
+ period_type = forms.ChoiceField(choices=(('duration', 'Duration until now'), ('date_date','Between two dates'), ('start_duration', 'Start date and duration')), widget=forms.RadioSelect(attrs={'onchange': mark_safe("show_hide_start_end_time(this.value);")}))
+ start_date = forms.DateTimeField(required=False, widget=AisCalendarWidget(attrs={'class':'vDateField'}))
+ duration = PeriodField(required=False, label='Period length', initial=7*86400)
+ end_date = forms.DateTimeField(required=False, widget=AisCalendarWidget(attrs={'class':'vDateField'}))
+ grain = PeriodField(label='One position every', initial=3600)
+
+ def clean_start_date(self):
+ period_type = self.cleaned_data.get('period_type', None)
+ start_date = self.cleaned_data.get('start_date', None)
+ if period_type in (u'date_date', u'start_duration') and start_date is None:
+ raise forms.ValidationError('That is field is required.')
+ return start_date
+
+ def clean_duration(self):
+ period_type = self.cleaned_data.get('period_type', None)
+ duration = self.cleaned_data.get('duration', None)
+ print 'duration=', duration
+ if period_type in (u'duration', u'start_duration') and duration is None:
+ raise forms.ValidationError('That is field is required.')
+ return duration
+
+ def clean_end_date(self):
+ period_type = self.cleaned_data.get('period_type', None)
+ end_date = self.cleaned_data.get('end_date', None)
+ if period_type in (u'date_date',) and end_date is None:
+ raise forms.ValidationError('That is field is required.')
+ return end_date
+
+ def clean(self):
+ cleaned_data = self.cleaned_data
+ period_type = self.cleaned_data.get('period_type', None)
+ start_date = self.cleaned_data.get('start_date', None)
+ #duration = self.cleaned_data.get('duration', None)
+ end_date = self.cleaned_data.get('end_date', None)
+ #if period_type in (u'date_date', u'start_duration') and start_date is None:
+ # self._errors["start_date"] = self.error_class(['That field is required.'])
+ #if period_type in (u'duration', u'start_duration') and duration is None:
+ # self._errors["duration"] = self.error_class(['That field is required.'])
+ #if period_type in (u'date_date',) and end_date is None:
+ # self._errors["end_date"] = self.error_class(['That field is required.'])
+ if period_type == u'date_date' and start_date is not None and end_date is not None:
+ if start_date <= end_date:
+ self._errors["start_date"] = self.error_class(['Start date must be before end date.'])
+ return cleaned_data
+
@http_authenticate(auth, 'ais')
def vessel(request, strmmsi):
mmsi = strmmsi_to_mmsi(strmmsi)
nmea = Nmea.new_from_lastinfo(strmmsi)
#if not nmea.timestamp_1 and not nmea.timestamp_5:
# raise Http404
- return render_to_response('vessel.html', {'nmea': nmea}, RequestContext(request))
+ return render_to_response('vessel.html', {'nmea': nmea, 'form': HistoryForm()}, RequestContext(request))
class VesselManualInputForm(forms.Form):
@http_authenticate(auth, 'ais')
-def vessel_history(request, strmmsi, format=None):
+def vessel_history(request, strmmsi):
"""
+ TODO
That view is called from Google Earth, so that it must support GET method.
"""
- ndays = request.REQUEST.get('ndays', None)
- if ndays is not None:
- try:
- ndays = int(ndays)
- except ValueError:
- ndays = 90
- period = ndays * 86400
- else:
- period = request.REQUEST.get('period', u'1')
- try:
- period = int(period)
- except ValueError:
- period = 1
- period_type = request.REQUEST.get('period_type', u'86400')
- try:
- period_type = int(period_type)
- except ValueError:
- period_type = 86400
-
- grain = request.REQUEST.get('grain', 1)
- try:
- grain = int(grain)
- except ValueError:
- grain = 1
- grain_type = request.REQUEST.get('grain_type', 3600)
- try:
- grain_type = int(grain_type)
- except ValueError:
- grain_type = 3600
-
- date_end = datetime.utcnow()
- date_start = date_end - timedelta(0,period*period_type)
- nmea_iterator = NmeaFeeder(strmmsi, date_end, date_start, granularity=grain*grain_type)
-
- format = request.REQUEST.get('format', u'track')
-
- if format == u'track':
- command = u'show_targets_ships --start=\'' + date_start.strftime('%Y%m%d %H%M%S') + u'\' --granularity=' + unicode(grain*grain_type) + ' --format=track '+ strmmsi
- extension = u'kmz'
-
- elif format == u'animation':
- command = u'show_targets_ships --start=\'' + date_start.strftime('%Y%m%d %H%M%S') + u'\' --granularity=' + unicode(grain*grain_type) + ' --format=animation '+ strmmsi
- extension = u'kmz'
-
- elif format == u'csv':
- command = u'common --start=\'' + date_start.strftime('%Y%m%d %H%M%S') + u'\' --granularity=' + unicode(grain*grain_type) + ' ' + strmmsi
- extension = u'csv'
- else:
- raise Http404(u'Invalid archive format')
-
- job = Job()
- job.friendly_filename = u'%s.%s' % (strmmsi, extension)
- job.user = request.user
- job.command = command
- job.save()
- if not jobrunner.wakeup_daemon():
- return HttpResponseServerError(jobrunner.DAEMON_WAKEUP_ERROR)
- return HttpResponseRedirect('/job/%s/download' % job.id)
+ initial = {}
+ if request.method == 'POST':
+ form = HistoryForm(request.POST, initial=initial)
+ if form.is_valid():
+ data = form.cleaned_data
+ if data['period_type'] == 'duration':
+ date_start = datetime.utcnow() - timedelta(0, data['duration'])
+ date_end = None # Now
+ elif data['period_type'] == 'date_date':
+ date_start = data['start_date']
+ date_end = data['end_date']
+ else:
+ assert data['period_type'] == 'start_duration', ('Invalid period type %s' % data['period_type'])
+ date_start = data['start_date']
+ date_end = date_start + timedelta(0, data['duration'])
+
+ grain = data['grain']
+
+ format = data['format']
+
+ if format == u'track':
+ command = u'show_targets_ships'
+ command += u' --format=track'
+ extension = u'kmz'
+
+ elif format == u'animation':
+ command = u'show_targets_ships'
+ command += u' --format=animation'
+ extension = u'kmz'
+
+ elif format == u'csv':
+ command = u'common'
+ extension = u'csv'
+ else:
+ raise Http404(u'Invalid archive format')
+
+ command += u' --start=\'' + date_start.strftime('%Y%m%d %H%M%S') + u'\''
+ if date_end:
+ command += u' --end=\'' + date_end.strftime('%Y%m%d %H%M%S') + u'\''
+ command += u' --granularity=' + unicode(grain)
+ command += u' ' + strmmsi
+
+ job = Job()
+ job.friendly_filename = u'%s.%s' % (strmmsi, extension)
+ job.user = request.user
+ job.command = command
+ job.save()
+ if not jobrunner.wakeup_daemon():
+ return HttpResponseServerError(jobrunner.DAEMON_WAKEUP_ERROR)
+ return HttpResponseRedirect('/job/%s/download' % job.id)
+ else: # GET
+ form = HistoryForm(initial=initial)
+ strmmsi = strmmsi.encode('utf-8')
+ nmea = Nmea.new_from_lastinfo(strmmsi)
+ return render_to_response('vessel_history.html', {'nmea': nmea, 'form':form}, RequestContext(request))
@http_authenticate(auth, 'ais')
--- /dev/null
+# -*- encoding: utf-8 -*-
+
+from django import forms
+
+class AisCalendarWidget(forms.TextInput):
+ class Media:
+ css = {
+ 'all': ('calendar.css',)
+ }
+ js = ('calendar.js', 'DateTimeShortcuts.js')
+
+
<link rel=stylesheet href='/global.css'>
<link rel=alternate type='application/rss+xml' title='All the news about AIS ship monitoring' href='https://ais.nirgal.com/news/feed'>
<title>{% block title %}AIS{% endblock %}</title>
-{% block style_extra %}{% endblock %}
<script src='/javascript/jquery/jquery.js' type='text/javascript'></script>
<script src='/global.js' type='text/javascript'></script>
+{% block style_extra %}{% endblock %}
<div id=header>
<span id=bannertitle>AIS ship monitoring</span>
--- /dev/null
+<span id=csvhint style="display:none;">Make sure you select "Charset: UTF-8" and "Separated by: Coma" when you <a href="/oocalc_howto.png">choose import options</a>.<br></span>
+
+<script type='text/javascript'>
+function show_hide_start_end_time(value) {
+ if (value=='duration') {
+ $('#id_start_date').attr('disabled', 'disabled');
+ $('#id_duration_0').removeAttr('disabled');
+ $('#id_duration_1').removeAttr('disabled');
+ $('#id_end_date').attr('disabled', 'disabled');
+ } else if (value=='date_date') {
+ $('#id_start_date').removeAttr('disabled');
+ $('#id_duration_0').attr('disabled', 'disabled');
+ $('#id_duration_1').attr('disabled', 'disabled');
+ $('#id_end_date').removeAttr('disabled');
+ } else if (value=='start_duration') {
+ $('#id_start_date').removeAttr('disabled');
+ $('#id_duration_0').removeAttr('disabled');
+ $('#id_duration_1').removeAttr('disabled');
+ $('#id_end_date').attr('disabled', 'disabled');
+ } else {
+ $('#id_start_date').attr('disabled', 'disabled');
+ $('#id_duration_0').attr('disabled', 'disabled');
+ $('#id_duration_1').attr('disabled', 'disabled');
+ $('#id_end_date').attr('disabled', 'disabled');
+ }
+}
+$(document).ready(function () {
+ show_hide_start_end_time($('input:radio[name=period_type]:checked').val());
+});
+</script>
+<form method=post action=history>
+{% include "fragment_formerror.html" %}
+<table>
+{{ form.as_table }}
+<tr><th><td>
+<input type=submit value=Get>
+</table>
+</form>
<a href="/job/{{ job.id }}/download" class=button>download</a>{% endif %}<br>
{% else %}
{% if job.start_time %}
- Status: <b>Running</b> since {{ job.start_time }}.<br>
+ Status: <b>Running</b> since {{ job.start_time|date:"Y-m-d H:i:s" }} UTC ( {{ job.running_time}} ) <br>
{% with job.get_stats as stats %}
Process ID: {{ stats.pid }}<br>
CPU ID: {{ stats.processor }}<br>
Nice: {{ stats.nice }}<br>
State: {{ stats.state }}<br>
Virtual size: {{ stats.vsize|filesizeformat }}<br>
- User time: {{ stats.utime }} ticks<br>
- System time: {{ stats.stime }} ticks<br>
+ {% comment %}
+ TODO:
+ "getconf CLK_TCK" = 100 -> 1 tick = 1/100 seconds
+ see Job.get_stat
+ {% endcomment %}
+ Time spent scheduled in user mode: {{ stats.utime }}00 ms<br>
+ Time spent scheduled in system mode: {{ stats.stime }}00 ms<br>
{% endwith %}
{% else %}
Status: <b>Queued</b> since {{ job.queue_time }}.<br>
{% extends "vessel_index.html" %}
+{% block style_extra %}
+{{ block.super }}
+{{ form.media }}
+{% endblock %}
+
{% block breadcrumbs %}
{{ block.super }}
/ <a href="/vessel/{{nmea.strmmsi}}/">{{nmea.strmmsi}}</a>
<h2>Get archive data</h2>
-<form action='history'>
-Format: <select name=format onchange="if (this.value=='csv') $('#csvhint').show(); else $('#csvhint').hide();">
-<option value=track>Track line (Google Earth)</option>
-<option value=animation>Animation (Google Earth)</option>
-<option value=csv>Coma separated values (SpreadSheet)</option>
-</select><br>
-
-For the last <input name=period size=3 value=7><select name=period_type>
-<option value=3600>hour(s)</option>
-<option value=86400 selected>day(s)</option>
-<option value=2592000>month(es)</option>
-</select>
-<br>
-One position every <input name=grain size=3 value=1><select name=grain_type>
-<option value=1>second(s)</option>
-<option value=60>minute(s)</option>
-<option value=3600 selected>hour(s)</option>
-<option value=86400>day(s)</option>
-</select>
-<br>
-<span id=csvhint style="display:none;">Make sure you select "Charset: UTF-8" and "Separated by: Coma" when you <a href="/oocalc_howto.png">choose import options</a>.<br></span>
-<input type=submit value=Get>
-</form>
-
+{% include "fragment_vessel_history.html" %}
{% endblock %}
--- /dev/null
+{% extends "vessel.html" %}
+
+{% block breadcrumbs %}
+{{ block.super }}
+/ <a href="/vessel/{{nmea.strmmsi}}/history">history</a>
+{% endblock %}
+
+{% block content %}
+
+<h2>Download archive data for {{ nmea.get_name }}</h2>
+
+{% include "fragment_vessel_history.html" %}
+
+{% endblock %}
--- /dev/null
+// Inserts shortcut buttons after all of the following:
+// <input type="text" class="vDateField">
+// <input type="text" class="vTimeField">
+
+var DateTimeShortcuts = {
+ calendars: [],
+ calendarInputs: [],
+ clockInputs: [],
+ calendarDivName1: 'calendarbox', // name of calendar <div> that gets toggled
+ calendarDivName2: 'calendarin', // name of <div> that contains calendar
+ calendarLinkName: 'calendarlink',// name of the link that is used to toggle
+ clockDivName: 'clockbox', // name of clock <div> that gets toggled
+ clockLinkName: 'clocklink', // name of the link that is used to toggle
+ admin_media_prefix: '',
+ init: function() {
+ // Deduce admin_media_prefix by looking at the <script>s in the
+ // current document and finding the URL of *this* module.
+ var scripts = document.getElementsByTagName('script');
+ for (var i=0; i<scripts.length; i++) {
+ if (scripts[i].src.match(/DateTimeShortcuts/)) {
+ //var idx = scripts[i].src.indexOf('js/admin/DateTimeShortcuts');
+ var idx = scripts[i].src.indexOf('DateTimeShortcuts');
+ DateTimeShortcuts.admin_media_prefix = scripts[i].src.substring(0, idx);
+ // NIRGAL WAS HERE:
+ DateTimeShortcuts.admin_media_prefix += 'media/'
+ break;
+ }
+ }
+
+ var inputs = document.getElementsByTagName('input');
+ for (i=0; i<inputs.length; i++) {
+ var inp = inputs[i];
+ if (inp.getAttribute('type') == 'text' && inp.className.match(/vTimeField/)) {
+ DateTimeShortcuts.addClock(inp);
+ }
+ else if (inp.getAttribute('type') == 'text' && inp.className.match(/vDateField/)) {
+ DateTimeShortcuts.addCalendar(inp);
+ }
+ }
+ },
+ // Add clock widget to a given field
+ addClock: function(inp) {
+ var num = DateTimeShortcuts.clockInputs.length;
+ DateTimeShortcuts.clockInputs[num] = inp;
+
+ // Shortcut links (clock icon and "Now" link)
+ var shortcuts_span = document.createElement('span');
+ inp.parentNode.insertBefore(shortcuts_span, inp.nextSibling);
+ var now_link = document.createElement('a');
+ now_link.setAttribute('href', "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().getHourMinuteSecond());");
+ now_link.appendChild(document.createTextNode(gettext('Now')));
+ var clock_link = document.createElement('a');
+ clock_link.setAttribute('href', 'javascript:DateTimeShortcuts.openClock(' + num + ');');
+ clock_link.id = DateTimeShortcuts.clockLinkName + num;
+ quickElement('img', clock_link, '', 'src', DateTimeShortcuts.admin_media_prefix + 'img/admin/icon_clock.gif', 'alt', gettext('Clock'));
+ shortcuts_span.appendChild(document.createTextNode('\240'));
+ shortcuts_span.appendChild(now_link);
+ shortcuts_span.appendChild(document.createTextNode('\240|\240'));
+ shortcuts_span.appendChild(clock_link);
+
+ // Create clock link div
+ //
+ // Markup looks like:
+ // <div id="clockbox1" class="clockbox module">
+ // <h2>Choose a time</h2>
+ // <ul class="timelist">
+ // <li><a href="#">Now</a></li>
+ // <li><a href="#">Midnight</a></li>
+ // <li><a href="#">6 a.m.</a></li>
+ // <li><a href="#">Noon</a></li>
+ // </ul>
+ // <p class="calendar-cancel"><a href="#">Cancel</a></p>
+ // </div>
+
+ var clock_box = document.createElement('div');
+ clock_box.style.display = 'none';
+ clock_box.style.position = 'absolute';
+ clock_box.className = 'clockbox module';
+ clock_box.setAttribute('id', DateTimeShortcuts.clockDivName + num);
+ document.body.appendChild(clock_box);
+ addEvent(clock_box, 'click', DateTimeShortcuts.cancelEventPropagation);
+
+ quickElement('h2', clock_box, gettext('Choose a time'));
+ time_list = quickElement('ul', clock_box, '');
+ time_list.className = 'timelist';
+ quickElement("a", quickElement("li", time_list, ""), gettext("Now"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().getHourMinuteSecond());")
+ quickElement("a", quickElement("li", time_list, ""), gettext("Midnight"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '00:00:00');")
+ quickElement("a", quickElement("li", time_list, ""), gettext("6 a.m."), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '06:00:00');")
+ quickElement("a", quickElement("li", time_list, ""), gettext("Noon"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '12:00:00');")
+
+ cancel_p = quickElement('p', clock_box, '');
+ cancel_p.className = 'calendar-cancel';
+ quickElement('a', cancel_p, gettext('Cancel'), 'href', 'javascript:DateTimeShortcuts.dismissClock(' + num + ');');
+ },
+ openClock: function(num) {
+ var clock_box = document.getElementById(DateTimeShortcuts.clockDivName+num)
+ var clock_link = document.getElementById(DateTimeShortcuts.clockLinkName+num)
+
+ // Recalculate the clockbox position
+ // is it left-to-right or right-to-left layout ?
+ if (getStyle(document.body,'direction')!='rtl') {
+ clock_box.style.left = findPosX(clock_link) + 17 + 'px';
+ }
+ else {
+ // since style's width is in em, it'd be tough to calculate
+ // px value of it. let's use an estimated px for now
+ // TODO: IE returns wrong value for findPosX when in rtl mode
+ // (it returns as it was left aligned), needs to be fixed.
+ clock_box.style.left = findPosX(clock_link) - 110 + 'px';
+ }
+ clock_box.style.top = findPosY(clock_link) - 30 + 'px';
+
+ // Show the clock box
+ clock_box.style.display = 'block';
+ addEvent(window, 'click', function() { DateTimeShortcuts.dismissClock(num); return true; });
+ },
+ dismissClock: function(num) {
+ document.getElementById(DateTimeShortcuts.clockDivName + num).style.display = 'none';
+ window.onclick = null;
+ },
+ handleClockQuicklink: function(num, val) {
+ DateTimeShortcuts.clockInputs[num].value = val;
+ DateTimeShortcuts.dismissClock(num);
+ },
+ // Add calendar widget to a given field.
+ addCalendar: function(inp) {
+ var num = DateTimeShortcuts.calendars.length;
+
+ DateTimeShortcuts.calendarInputs[num] = inp;
+
+ // Shortcut links (calendar icon and "Today" link)
+ var shortcuts_span = document.createElement('span');
+ inp.parentNode.insertBefore(shortcuts_span, inp.nextSibling);
+ var today_link = document.createElement('a');
+ today_link.setAttribute('href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', 0);');
+ today_link.appendChild(document.createTextNode(gettext('Today')));
+ var cal_link = document.createElement('a');
+ cal_link.setAttribute('href', 'javascript:DateTimeShortcuts.openCalendar(' + num + ');');
+ cal_link.id = DateTimeShortcuts.calendarLinkName + num;
+ quickElement('img', cal_link, '', 'src', DateTimeShortcuts.admin_media_prefix + 'img/admin/icon_calendar.gif', 'alt', gettext('Calendar'));
+ shortcuts_span.appendChild(document.createTextNode('\240'));
+ shortcuts_span.appendChild(today_link);
+ shortcuts_span.appendChild(document.createTextNode('\240|\240'));
+ shortcuts_span.appendChild(cal_link);
+
+ // Create calendarbox div.
+ //
+ // Markup looks like:
+ //
+ // <div id="calendarbox3" class="calendarbox module">
+ // <h2>
+ // <a href="#" class="link-previous">‹</a>
+ // <a href="#" class="link-next">›</a> February 2003
+ // </h2>
+ // <div class="calendar" id="calendarin3">
+ // <!-- (cal) -->
+ // </div>
+ // <div class="calendar-shortcuts">
+ // <a href="#">Yesterday</a> | <a href="#">Today</a> | <a href="#">Tomorrow</a>
+ // </div>
+ // <p class="calendar-cancel"><a href="#">Cancel</a></p>
+ // </div>
+ var cal_box = document.createElement('div');
+ cal_box.style.display = 'none';
+ cal_box.style.position = 'absolute';
+ cal_box.className = 'calendarbox module';
+ cal_box.setAttribute('id', DateTimeShortcuts.calendarDivName1 + num);
+ document.body.appendChild(cal_box);
+ addEvent(cal_box, 'click', DateTimeShortcuts.cancelEventPropagation);
+
+ // next-prev links
+ var cal_nav = quickElement('div', cal_box, '');
+ var cal_nav_prev = quickElement('a', cal_nav, '<', 'href', 'javascript:DateTimeShortcuts.drawPrev('+num+');');
+ cal_nav_prev.className = 'calendarnav-previous';
+ var cal_nav_next = quickElement('a', cal_nav, '>', 'href', 'javascript:DateTimeShortcuts.drawNext('+num+');');
+ cal_nav_next.className = 'calendarnav-next';
+
+ // main box
+ var cal_main = quickElement('div', cal_box, '', 'id', DateTimeShortcuts.calendarDivName2 + num);
+ cal_main.className = 'calendar';
+ DateTimeShortcuts.calendars[num] = new Calendar(DateTimeShortcuts.calendarDivName2 + num, DateTimeShortcuts.handleCalendarCallback(num));
+ DateTimeShortcuts.calendars[num].drawCurrent();
+
+ // calendar shortcuts
+ var shortcuts = quickElement('div', cal_box, '');
+ shortcuts.className = 'calendar-shortcuts';
+ quickElement('a', shortcuts, gettext('Yesterday'), 'href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', -1);');
+ shortcuts.appendChild(document.createTextNode('\240|\240'));
+ quickElement('a', shortcuts, gettext('Today'), 'href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', 0);');
+ shortcuts.appendChild(document.createTextNode('\240|\240'));
+ quickElement('a', shortcuts, gettext('Tomorrow'), 'href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', +1);');
+
+ // cancel bar
+ var cancel_p = quickElement('p', cal_box, '');
+ cancel_p.className = 'calendar-cancel';
+ quickElement('a', cancel_p, gettext('Cancel'), 'href', 'javascript:DateTimeShortcuts.dismissCalendar(' + num + ');');
+ },
+ openCalendar: function(num) {
+ var cal_box = document.getElementById(DateTimeShortcuts.calendarDivName1+num)
+ var cal_link = document.getElementById(DateTimeShortcuts.calendarLinkName+num)
+ var inp = DateTimeShortcuts.calendarInputs[num];
+
+ // Determine if the current value in the input has a valid date.
+ // If so, draw the calendar with that date's year and month.
+ if (inp.value) {
+ var date_parts = inp.value.split('-');
+ var year = date_parts[0];
+ var month = parseFloat(date_parts[1]);
+ if (year.match(/\d\d\d\d/) && month >= 1 && month <= 12) {
+ DateTimeShortcuts.calendars[num].drawDate(month, year);
+ }
+ }
+
+
+ // Recalculate the clockbox position
+ // is it left-to-right or right-to-left layout ?
+ if (getStyle(document.body,'direction')!='rtl') {
+ cal_box.style.left = findPosX(cal_link) + 17 + 'px';
+ }
+ else {
+ // since style's width is in em, it'd be tough to calculate
+ // px value of it. let's use an estimated px for now
+ // TODO: IE returns wrong value for findPosX when in rtl mode
+ // (it returns as it was left aligned), needs to be fixed.
+ cal_box.style.left = findPosX(cal_link) - 180 + 'px';
+ }
+ cal_box.style.top = findPosY(cal_link) - 75 + 'px';
+
+ cal_box.style.display = 'block';
+ addEvent(window, 'click', function() { DateTimeShortcuts.dismissCalendar(num); return true; });
+ },
+ dismissCalendar: function(num) {
+ document.getElementById(DateTimeShortcuts.calendarDivName1+num).style.display = 'none';
+ },
+ drawPrev: function(num) {
+ DateTimeShortcuts.calendars[num].drawPreviousMonth();
+ },
+ drawNext: function(num) {
+ DateTimeShortcuts.calendars[num].drawNextMonth();
+ },
+ handleCalendarCallback: function(num) {
+ return "function(y, m, d) { DateTimeShortcuts.calendarInputs["+num+"].value = y+'-'+(m<10?'0':'')+m+'-'+(d<10?'0':'')+d; document.getElementById(DateTimeShortcuts.calendarDivName1+"+num+").style.display='none';}";
+ },
+ handleCalendarQuickLink: function(num, offset) {
+ var d = new Date();
+ d.setDate(d.getDate() + offset)
+ DateTimeShortcuts.calendarInputs[num].value = d.getISODate();
+ DateTimeShortcuts.dismissCalendar(num);
+ },
+ cancelEventPropagation: function(e) {
+ if (!e) e = window.event;
+ e.cancelBubble = true;
+ if (e.stopPropagation) e.stopPropagation();
+ }
+}
+
+addEvent(window, 'load', DateTimeShortcuts.init);
--- /dev/null
+/* DATE AND TIME */
+p.datetime { line-height:20px; margin:0; padding:0; color:#666; font-size:11px; font-weight:bold; }
+.datetime span { font-size:11px; color:#ccc; font-weight:normal; white-space:nowrap; }
+table p.datetime { font-size:10px; margin-left:0; padding-left:0; }
+
+/* CALENDARS & CLOCKS */
+.calendarbox, .clockbox { margin:5px auto; font-size:11px; width:16em; text-align:center; background:white; position:relative; }
+.clockbox { width:auto; }
+.calendar { margin:0; padding: 0; }
+.calendar table { margin:0; padding:0; border-collapse:collapse; background:white; width:99%; }
+.calendar caption, .calendarbox h2 { margin: 0; font-size:11px; text-align:center; border-top:none; }
+.calendar th { font-size:10px; color:#666; padding:2px 3px; text-align:center; background:#e1e1e1 url(/media/img/admin/nav-bg.gif) 0 50% repeat-x; border-bottom:1px solid #ddd; }
+.calendar td { font-size:11px; text-align: center; padding: 0; border-top:1px solid #eee; border-bottom:none; }
+.calendar td.selected a { background: #C9DBED; }
+.calendar td.nonday { background:#efefef; }
+.calendar td.today a { background:#ffc; }
+.calendar td a, .timelist a { display: block; font-weight:bold; padding:4px; text-decoration: none; color:#444; }
+.calendar td a:hover, .timelist a:hover { background: #5b80b2; color:white; }
+.calendar td a:active, .timelist a:active { background: #036; color:white; }
+.calendarnav { font-size:10px; text-align: center; color:#ccc; margin:0; padding:1px 3px; }
+.calendarnav a:link, #calendarnav a:visited, #calendarnav a:hover { color: #999; }
+.calendar-shortcuts { background:white; font-size:10px; line-height:11px; border-top:1px solid #eee; padding:3px 0 4px; color:#ccc; }
+.calendarbox .calendarnav-previous, .calendarbox .calendarnav-next { display:block; position:absolute; font-weight:bold; font-size:12px; background:#C9DBED url(/media/img/admin/default-bg.gif) bottom left repeat-x; padding:1px 4px 2px 4px; color:white; }
+.calendarnav-previous:hover, .calendarnav-next:hover { background:#036; }
+.calendarnav-previous { top:0; left:0; }
+.calendarnav-next { top:0; right:0; }
+.calendar-cancel { margin:0 !important; padding:0; font-size:10px; background:#e1e1e1 url(/media/img/admin/nav-bg.gif) 0 50% repeat-x; border-top:1px solid #ddd; }
+.calendar-cancel a { padding:2px; color:#999; }
+ul.timelist, .timelist li { list-style-type:none; margin:0; padding:0; }
+.timelist a { padding:2px; }
+
+
--- /dev/null
+/*
+calendar.js - Calendar functions by Adrian Holovaty
+*/
+
+function removeChildren(a) { // "a" is reference to an object
+ while (a.hasChildNodes()) a.removeChild(a.lastChild);
+}
+
+// quickElement(tagType, parentReference, textInChildNode, [, attribute, attributeValue ...]);
+function quickElement() {
+ var obj = document.createElement(arguments[0]);
+ if (arguments[2] != '' && arguments[2] != null) {
+ var textNode = document.createTextNode(arguments[2]);
+ obj.appendChild(textNode);
+ }
+ var len = arguments.length;
+ for (var i = 3; i < len; i += 2) {
+ obj.setAttribute(arguments[i], arguments[i+1]);
+ }
+ arguments[1].appendChild(obj);
+ return obj;
+}
+
+// CalendarNamespace -- Provides a collection of HTML calendar-related helper functions
+var CalendarNamespace = {
+ monthsOfYear: gettext('January February March April May June July August September October November December').split(' '),
+ daysOfWeek: gettext('S M T W T F S').split(' '),
+ isLeapYear: function(year) {
+ return (((year % 4)==0) && ((year % 100)!=0) || ((year % 400)==0));
+ },
+ getDaysInMonth: function(month,year) {
+ var days;
+ if (month==1 || month==3 || month==5 || month==7 || month==8 || month==10 || month==12) {
+ days = 31;
+ }
+ else if (month==4 || month==6 || month==9 || month==11) {
+ days = 30;
+ }
+ else if (month==2 && CalendarNamespace.isLeapYear(year)) {
+ days = 29;
+ }
+ else {
+ days = 28;
+ }
+ return days;
+ },
+ draw: function(month, year, div_id, callback) { // month = 1-12, year = 1-9999
+ month = parseInt(month);
+ year = parseInt(year);
+ var calDiv = document.getElementById(div_id);
+ removeChildren(calDiv);
+ var calTable = document.createElement('table');
+ quickElement('caption', calTable, CalendarNamespace.monthsOfYear[month-1] + ' ' + year);
+ var tableBody = quickElement('tbody', calTable);
+
+ // Draw days-of-week header
+ var tableRow = quickElement('tr', tableBody);
+ for (var i = 0; i < 7; i++) {
+ quickElement('th', tableRow, CalendarNamespace.daysOfWeek[i]);
+ }
+
+ var startingPos = new Date(year, month-1, 1).getDay();
+ var days = CalendarNamespace.getDaysInMonth(month, year);
+
+ // Draw blanks before first of month
+ tableRow = quickElement('tr', tableBody);
+ for (var i = 0; i < startingPos; i++) {
+ var _cell = quickElement('td', tableRow, ' ');
+ _cell.style.backgroundColor = '#f3f3f3';
+ }
+
+ // Draw days of month
+ var currentDay = 1;
+ for (var i = startingPos; currentDay <= days; i++) {
+ if (i%7 == 0 && currentDay != 1) {
+ tableRow = quickElement('tr', tableBody);
+ }
+ var cell = quickElement('td', tableRow, '');
+ quickElement('a', cell, currentDay, 'href', 'javascript:void(' + callback + '('+year+','+month+','+currentDay+'));');
+ currentDay++;
+ }
+
+ // Draw blanks after end of month (optional, but makes for valid code)
+ while (tableRow.childNodes.length < 7) {
+ var _cell = quickElement('td', tableRow, ' ');
+ _cell.style.backgroundColor = '#f3f3f3';
+ }
+
+ calDiv.appendChild(calTable);
+ }
+}
+
+// Calendar -- A calendar instance
+function Calendar(div_id, callback) {
+ // div_id (string) is the ID of the element in which the calendar will
+ // be displayed
+ // callback (string) is the name of a JavaScript function that will be
+ // called with the parameters (year, month, day) when a day in the
+ // calendar is clicked
+ this.div_id = div_id;
+ this.callback = callback;
+ this.today = new Date();
+ this.currentMonth = this.today.getMonth() + 1;
+ this.currentYear = this.today.getFullYear();
+}
+Calendar.prototype = {
+ drawCurrent: function() {
+ CalendarNamespace.draw(this.currentMonth, this.currentYear, this.div_id, this.callback);
+ },
+ drawDate: function(month, year) {
+ this.currentMonth = month;
+ this.currentYear = year;
+ this.drawCurrent();
+ },
+ drawPreviousMonth: function() {
+ if (this.currentMonth == 1) {
+ this.currentMonth = 12;
+ this.currentYear--;
+ }
+ else {
+ this.currentMonth--;
+ }
+ this.drawCurrent();
+ },
+ drawNextMonth: function() {
+ if (this.currentMonth == 12) {
+ this.currentMonth = 1;
+ this.currentYear++;
+ }
+ else {
+ this.currentMonth++;
+ }
+ this.drawCurrent();
+ },
+ drawPreviousYear: function() {
+ this.currentYear--;
+ this.drawCurrent();
+ },
+ drawNextYear: function() {
+ this.currentYear++;
+ this.drawCurrent();
+ }
+}
font-size: 70%;
}
+a img {
+ border:none;
+}
+
a[href] {
text-decoration: none;
/*color: #4444bb;*/
list-style-image: url('/errorbullet.png');
}
+td li {
+ list-style: none;
+}
+
div.message {
background:#ffffd0;
border: 1px solid yellow;
$(document).ready(check_footer_bottom);
$(window).resize(check_footer_bottom);
+
+
+
+
+//-------- dummy i18n.js
+function gettext(txt) {
+ return txt;
+}
+
+//-------- core.js
+// Core javascript helper functions
+
+// basic browser identification & version
+var isOpera = (navigator.userAgent.indexOf("Opera")>=0) && parseFloat(navigator.appVersion);
+var isIE = ((document.all) && (!isOpera)) && parseFloat(navigator.appVersion.split("MSIE ")[1].split(";")[0]);
+
+// Cross-browser event handlers.
+function addEvent(obj, evType, fn) {
+ if (obj.addEventListener) {
+ obj.addEventListener(evType, fn, false);
+ return true;
+ } else if (obj.attachEvent) {
+ var r = obj.attachEvent("on" + evType, fn);
+ return r;
+ } else {
+ return false;
+ }
+}
+
+function removeEvent(obj, evType, fn) {
+ if (obj.removeEventListener) {
+ obj.removeEventListener(evType, fn, false);
+ return true;
+ } else if (obj.detachEvent) {
+ obj.detachEvent("on" + evType, fn);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+// quickElement(tagType, parentReference, textInChildNode, [, attribute, attributeValue ...]);
+function quickElement() {
+ var obj = document.createElement(arguments[0]);
+ if (arguments[2] != '' && arguments[2] != null) {
+ var textNode = document.createTextNode(arguments[2]);
+ obj.appendChild(textNode);
+ }
+ var len = arguments.length;
+ for (var i = 3; i < len; i += 2) {
+ obj.setAttribute(arguments[i], arguments[i+1]);
+ }
+ arguments[1].appendChild(obj);
+ return obj;
+}
+
+// ----------------------------------------------------------------------------
+// Find-position functions by PPK
+// See http://www.quirksmode.org/js/findpos.html
+// ----------------------------------------------------------------------------
+function findPosX(obj) {
+ var curleft = 0;
+ if (obj.offsetParent) {
+ while (obj.offsetParent) {
+ curleft += obj.offsetLeft - ((isOpera) ? 0 : obj.scrollLeft);
+ obj = obj.offsetParent;
+ }
+ // IE offsetParent does not include the top-level
+ if (isIE && obj.parentElement){
+ curleft += obj.offsetLeft - obj.scrollLeft;
+ }
+ } else if (obj.x) {
+ curleft += obj.x;
+ }
+ return curleft;
+}
+
+function findPosY(obj) {
+ var curtop = 0;
+ if (obj.offsetParent) {
+ while (obj.offsetParent) {
+ curtop += obj.offsetTop - ((isOpera) ? 0 : obj.scrollTop);
+ obj = obj.offsetParent;
+ }
+ // IE offsetParent does not include the top-level
+ if (isIE && obj.parentElement){
+ curtop += obj.offsetTop - obj.scrollTop;
+ }
+ } else if (obj.y) {
+ curtop += obj.y;
+ }
+ return curtop;
+}
+
+//-----------------------------------------------------------------------------
+// Date object extensions
+// ----------------------------------------------------------------------------
+Date.prototype.getCorrectYear = function() {
+ // Date.getYear() is unreliable --
+ // see http://www.quirksmode.org/js/introdate.html#year
+ var y = this.getYear() % 100;
+ return (y < 38) ? y + 2000 : y + 1900;
+}
+
+Date.prototype.getTwoDigitMonth = function() {
+ return (this.getMonth() < 9) ? '0' + (this.getMonth()+1) : (this.getMonth()+1);
+}
+
+Date.prototype.getTwoDigitDate = function() {
+ return (this.getDate() < 10) ? '0' + this.getDate() : this.getDate();
+}
+
+Date.prototype.getTwoDigitHour = function() {
+ return (this.getHours() < 10) ? '0' + this.getHours() : this.getHours();
+}
+
+Date.prototype.getTwoDigitMinute = function() {
+ return (this.getMinutes() < 10) ? '0' + this.getMinutes() : this.getMinutes();
+}
+
+Date.prototype.getTwoDigitSecond = function() {
+ return (this.getSeconds() < 10) ? '0' + this.getSeconds() : this.getSeconds();
+}
+
+Date.prototype.getISODate = function() {
+ return this.getCorrectYear() + '-' + this.getTwoDigitMonth() + '-' + this.getTwoDigitDate();
+}
+
+Date.prototype.getHourMinute = function() {
+ return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute();
+}
+
+Date.prototype.getHourMinuteSecond = function() {
+ return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute() + ':' + this.getTwoDigitSecond();
+}
+
+// ----------------------------------------------------------------------------
+// String object extensions
+// ----------------------------------------------------------------------------
+String.prototype.pad_left = function(pad_length, pad_string) {
+ var new_string = this;
+ for (var i = 0; new_string.length < pad_length; i++) {
+ new_string = pad_string + new_string;
+ }
+ return new_string;
+}
+
+// ----------------------------------------------------------------------------
+// Get the computed style for and element
+// ----------------------------------------------------------------------------
+function getStyle(oElm, strCssRule){
+ var strValue = "";
+ if(document.defaultView && document.defaultView.getComputedStyle){
+ strValue = document.defaultView.getComputedStyle(oElm, "").getPropertyValue(strCssRule);
+ }
+ else if(oElm.currentStyle){
+ strCssRule = strCssRule.replace(/\-(\w)/g, function (strMatch, p1){
+ return p1.toUpperCase();
+ });
+ strValue = oElm.currentStyle[strCssRule];
+ }
+ return strValue;
+}