Commit f6fdeffe by Őry Máté

Merge branch 'release-13.03.2' into releases

parents 0d188bd6 d9588334
......@@ -5,7 +5,7 @@ jsfiles += one/static/script/util.min.js
jsfiles += one/static/script/store.min.js
cssfiles += one/static/style/style.css
default: migrate generatestatic collectstatic mo restart
default: migrate generatestatic collectstatic mo setbranch restart
pulldef: pull default
pull:
......@@ -23,6 +23,10 @@ generatestatic: $(jsfiles) $(cssfiles)
collectstatic:
./manage.py collectstatic --noinput
setbranch:
sed -i cloud/local_settings.py -e '/RELEASE=/d' || true
echo "RELEASE='`git rev-parse --abbrev-ref HEAD`'" >>cloud/local_settings.py
mo:
for i in */locale/*/*/*.po; do echo -ne "$$i:\t"; msgfmt --statistics $$i;done
for i in */; do cd $$i; ls locale &>/dev/null && ../manage.py compilemessages || true; cd ..; done
......
from cloud.settings import DEBUG
from cloud.settings import DEBUG, STAT_DEBUG, RELEASE
from django.core.cache import cache
import subprocess
import json
def process_debug(req):
return {'DEBUG': DEBUG}
def process_stat(req):
if STAT_DEBUG:
stat = {
'CPU': {
'USED_CPU': 10,
'ALLOC_CPU': 20,
'FREE_CPU': 70
},
'MEM': {
'USED_MEM': 567,
'ALLOC_MEM': 371,
'FREE_MEM': 2048-567-371
}
}
else:
stat = cache.get('cloud_stat')
return {
'STAT_DEBUG': STAT_DEBUG,
'cloud_stat': stat,
}
def process_release(req):
return {
'release': RELEASE,
}
......@@ -3,6 +3,7 @@
DEBUG = True
TEMPLATE_DEBUG = DEBUG
STAT_DEBUG = True
ADMINS = (
('IK', 'cloud@cloud.ik.bme.hu'),
......@@ -119,6 +120,8 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.tz',
'django.contrib.messages.context_processors.messages',
'cloud.context_processors.process_debug',
'cloud.context_processors.process_stat',
'cloud.context_processors.process_release',
)
TEMPLATE_DIRS = (
......@@ -200,8 +203,17 @@ CELERY_ROUTES = {
'firewall.tasks.reload_blacklist_task': {'queue': 'firewall'},
'firewall.tasks.Periodic': {'queue': 'local'},
'one.tasks.SendMailTask': {'queue': 'local'},
'one.tasks.UpdateInstanceStateTask': {'queue': 'local'}
}
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211',
}
}
store_settings = {
"basic_auth": "True",
"verify_ssl": "False",
......@@ -225,6 +237,7 @@ firewall_settings = {
EMAIL_HOST='152.66.243.92' # giccero ipv4
CLOUD_URL='https://cloud.ik.bme.hu/'
RELEASE='master'
try:
from cloud.local_settings import *
......
{% extends "base.html" %}
{% load i18n %}
{% get_current_language as LANGUAGE_CODE %}
{% block content %}
<div class="irasmu">
<h2>Változtatások listája</h2>
<section>
<p>Az IK Cloud fejlesztése kéthetes ciklusokban történik, az rendes kiadások élesítése
páros heteken csütörtök este zajlik. Alább foglaljuk össze az egyes
kiadások főbb, a felhasználók által is látható változtatásait.</p>
</section>
<a id="master"></a>
<section>
<h3 id="release-13.03.2"><a href="#13.03.2">13.03.2 (2013. március 21.)</a></h3>
<ul>
<li>Súgó.</li>
<li>Változáslista.</li>
<li>Reszponzív és folyékony elrendezés.</li>
<li>Statikus oldalak új külalakja.</li>
<li>Gyorsabb oldalbetöltés.</li>
<li>Statisztika a cluster állapotáról.</li>
</ul>
</section>
<section>
<h3 id="release-13.03.1"><a href="#13.03.1">13.03.1 (2013. március 7.)</a></h3>
<ul>
<li>Határidős felfüggesztés élesítve.</li>
<li>Csatlakozási adatoknál IP cím helyett DNS név jelenik meg.</li>
<li>Ha a portált IPV6-on érik el, a csatlakozási adatoknál egyedi DNS név és publikus port jelenik meg.</li>
<li>Legújabb fájloktól vissza lehet lépni az összeshez.</li>
<li>Bemutató képernyő elérhető bejelentkezve is.</li>
<li>Megosztás adatai szerkeszthetőek.</li>
<li>Sablon adatai szerkeszthetőek.</li>
<li>Lábléc; impresszum, szabályzat, támogatás oldalak.</li>
<li>Megosztásnál is látszik a géptípus.</li>
<li>Dobozok rejthetőek.</li>
<li>Sablon mentésének menete gördülékenyebb.</li>
<li>Jelszómegjelenítés javítva.</li>
<li>Fájlok rendezése működik.</li>
<li>Segítség-dobozok bővítve.</li>
<li>Szebb HTTP hibaüzenetek.</li>
<li>Kulcs hozzáadásának visszaigazolása.</li>
<li>Sablonok állapotának helyes kijelzése.</li>
<li>Minimalizált js és css kód.</li>
</ul>
</section>
<section>
<h3 id="release-13.02.2"><a href="#13.02.2">13.02.2 (2013. február 21.)</a></h3>
<ul>
<li>Felhasználói kvóták megvalósítása.</li>
<li>Publikus kulcsok kezelése.</li>
<li>További részletek gombok.</li>
<li>Saját csoportok rejthetőek.</li>
<li>Segítség-dobozok a legtöbb helyre.</li>
<li>Csoporttulajdonosok kezelése, több adat megjelenítése.</li>
<li>VM átnevezhető.</li>
</ul>
</section>
</div>
{% endblock %}
......@@ -5,8 +5,13 @@
{% block content %}
<div class="irasmu">
<h2>Impresszum</h2>
<section>
<p>Az IK Cloud a BME IK és IIT együttműködésében, a VIK támogatásával létrejött rendszer, amelyben az IIT oktatói és hallgatói szükség szerint vehetnek igénybe virtuális erőforrásokat.
</p>
</section>
<section>
<p>Az oldal üzemeltetője a BME Közigazgatási Informatikai Központ.</p>
<p>A rendszer fejlesztésében részt vettek:
Bach Dániel,
......@@ -17,6 +22,7 @@
dr. Szeberényi Imre.</p>
<p>A rendszer HP hardveren, Ubuntu operációs rendszeren, KVM virtualizációval, Open vSwitch virtuális hálózaton, libvirt köztes réteggel, OpenNebula cloud menedzserrel, Django alapú webportállal működik.</p>
<p>Some icons by <a href="http://p.yusukekamiyamane.com/">Yusuke Kamiyamane</a>. Licensed under a <a href="http://creativecommons.org/licenses/by/3.0/">Creative Commons Attribution 3.0 License</a>.</p>
</section>
</div>
{% endblock %}
......@@ -5,6 +5,8 @@
{% block content %}
<div class="irasmu">
<h2>Felhasználási feltételek</h2>
<section>
<p>Az IK Cloud a BME IK és IIT együttműködésében, a VIK támogatásával létrejött rendszer, amelyben az IIT oktatói és hallgatói szükség szerint vehetnek igénybe <strong>virtuális erőforrásokat</strong>.</p>
<p>Az IIT géptermeiben telepített kliensszoftver segítségével az oktató által előkészített és kiajánlott <strong>virtuális környezetet a hallgató</strong> a számára megszabott <strong>kvóta keretein belül</strong> igény szerint indíthatja.</p>
......@@ -14,11 +16,15 @@
<p>Lehetőség van a gépteremben elkezdett munka otthoni folytatására is a korábban elindított környezetben. A rendszer segítségével az otthoni feladatot végző hallgató is elkerülheti az összetett szoftverkörnyezet telepítésével járó kellemetlenségeket, figyelmét a szakmai munkára irányíthatja.</p>
<p>A rendszer segítséget nyújt önálló labor, szakdolgozat, diplomaterv vagy tdk-munka készítésénél is: a munkához szükséges virtuális gépeket az önkiszolgáló rendszeren keresztül azonnal használatba lehet venni.</p>
</section>
<section>
<p>Az elindított virtuális gépekre az adott operációs rendszeren szokásos módon lehet távolról csatlakozni: Windows esetén RDP (távoli asztal), Linux esetén SSH, vagy grafikusan NoMachine NX segítségével. A kapcsolódást a géptermekben telepített kliensszoftver még kényelmesebbé teszi.</p>
<p>Az elindított gépeken a <strong>felhasználói adattár</strong> automatikusan csatolásra kerül. Minden munkát itt érdemes végezni, mivel az a gép leállítása után is elérhető marad.</p>
</section>
<section>
<p>A felhasználók a rendszert jelen önkiszolgáló felületen keresztül érhetik el, amelybe az egyetemi címtárszolgáltatás (eduID) segítségével léphetnek be. A továbblépéssel elfogadják a következőekben összefoglalt <strong>felhasználási feltételeket</strong>.</p>
<ul>
<li>A rendszer <strong>nem használható</strong> semmilyen jogszabályba, vagy <a href="http://net.bme.hu/regula/">egyetemi</a> <strong>szabályzatba ütköző tevékenységre</strong>.</li>
......@@ -29,6 +35,7 @@
<li>A rendszert üzemeltető BME IK lehetőségeihez mérten mindent megtesz a szolgáltatásbiztos üzemért, az egyetemi infrastruktúra sajátosságai miatt előforduló üzemszünetekre azonban számítani kell. A BME IK az üzemeltetés kapcsán a folyamatos üzem sérüléséért vagy adatvesztésért <strong>minden felelősséget kizár</strong>. Ezeknek megfelelően szükség esetén a biztonsági mentés és a magas rendelkezésre állású környezet kialakítása a felhasználóra hárul.</li>
</ul>
<p>A szabályok betartása közös érdekünk. Reméljük, hogy a rendszerünk használata megkönnyíti munkájukat, melyhez sok sikert kívánunk!</p>
</section>
</div>
{% endblock %}
......@@ -5,6 +5,8 @@
{% block content %}
<div class="irasmu">
<h2>Támogatás</h2>
<section>
<p>A rendszer használatával kapcsolatos kérdéseket, általános észrevételeket a <tt>cloud <em>(kukac)</em>
ik.bme.hu</tt> e-mail címen várjuk.
Ugyancsak örömmel fogadjuk a rendszer használatával kapcsolatos beszámolókat.</p>
......@@ -12,5 +14,7 @@
<a href="https://giccero.cloud.ik.bme.hu/trac/cloud/newticket" rel="nofollow">
hibajegy felvételével</a> jelezze.
</p>
</section>
</div>
{% endblock %}
#!/usr/bin/env python
from celery import Celery, task
import subprocess
import time, re
import socket
import sys
BROKER_URL = 'amqp://nyuszi:teszt@localhost:5672/django'
try:
from local_settings import *
except:
pass
CELERY_CREATE_MISSING_QUEUES=True
celery = Celery('tasks', broker=BROKER_URL)
def main(argv):
celery.send_task('one.tasks.UpdateInstanceStateTask', [ int(sys.argv[1]),
], queue='local')
if __name__ == "__main__":
main(sys.argv)
......@@ -5,8 +5,16 @@ echo "En vagyok a $0 !"
arr=( $((echo 'ibase=16' ; ifconfig eth0 | awk '/HWaddr/ {print $5}' | tr ':a-z' '\nA-Z') | bc ) )
ipv4="${arr[2]}.${arr[3]}.${arr[4]}.${arr[5]}"
gw4="${arr[2]}.${arr[3]}.255.254"
net4="255.255.0.0"
ipv6="2001:738:2001:4031:${arr[3]}:${arr[4]}:${arr[5]}:0"
gw6="2001:738:2001:4031:${arr[3]}:255:254:0"
net6="80"
if [ "${arr[2]}" == "152" ]; then
gw4="${arr[2]}.${arr[3]}.243.126"
net4="255.255.255.192"
gw6="2001:738:2001:4031:66:243:126:0"
fi
echo ok "$ipv4 $ipv6 $gw4 $gw6"
......@@ -20,12 +28,12 @@ iface lo inet loopback
auto eth0
iface eth0 inet static
address $ipv4
netmask 255.255.0.0
netmask $net4
gateway $gw4
dns-nameservers 152.66.243.60
iface eth0 inet6 static
address $ipv6
netmask 80
netmask $net6
gateway $gw6
EOF
......@@ -46,10 +54,10 @@ DNS1="152.66.243.60"
PEERDNS="yes"
IPADDR="$ipv4"
NETMASK="255.255.0.0"
NETMASK="$net4"
GATEWAY=$gw4
IPV6ADDR="$ipv6/80"
IPV6ADDR="$ipv6/$net6"
IPV6_DEFAULTGW="$gw6"
EOF
......
......@@ -6,12 +6,13 @@ do
sudo stop $i || true
done
sudo apt-get install rabbitmq-server gettext
sudo apt-get install rabbitmq-server gettext memcached
sudo rabbitmqctl delete_user guest || true
sudo rabbitmqctl add_user nyuszi teszt || true
sudo rabbitmqctl add_vhost django || true
sudo rabbitmqctl set_permissions -p django nyuszi '.*' '.*' '.*' || true
sudo pip install python-memcached
sudo cp /opt/webadmin/cloud/miscellaneous/devenv/boot_url.py /opt/
......
#!/bin/bash
# install xml.vim
mkdir -p ~/.vim/{ftplugin,indent}
cd ~/.vim
wget 'http://www.vim.org/scripts/download_script.php?src_id=16073' -O ftplugin/xml.vim
echo 'let b:did_indent = 1' > indent/xml.vim
for i in docbk xsl html xhtml
do
ln -s xml.vim ftplugin/$i.vim
echo 'let b:did_indent = 1' > indent/$i.vim
done
\documentclass[12pt,a4paper]{article}
\title{Harnessing Wasted Computing Power for Scientific Computing}
\author{S\'andor Guba, M\'at\'e \H{O}ry and Imre Szeber\'enyi\\
Budapest University of Technology and Economics, %\\
%Magyar Tud\'osok k\"or\'utja 2, H-1117 Budapest,
Hungary}
\date{\empty}
\begin{document}
\maketitle
Nowadays more and more general purpose workstations installed in a student
laboratory have built in multi-core CPU and graphics card providing significant
computing power. In most cases the utilization of these resources is low, and
limited to lecture hours. The concept of utility computing plays an important
role in nowadays technological development. As part of utility computing, cloud
computing offers greater flexibility and responsiveness to ICT users at lower
cost.
In  this paper, we introduce a cloud management system which enables the
simultaneous use of both dedicated resources and opportunistic environment. All
the free workstations (powered or not) are automatically added to a resource
pool, and can be used like ordinary cloud resources. Researchers can launch
various virtualized software appliances. Our solution leverages the advantages
of HTCondor and OpenNebula systems.
Modern graphics processing units (GPUs) with many-core architectures have
emerged as general-purpose parallel computing platforms that can dramatically
accelerate  scientific applications used for various simulations. Our business
model harnesses computing power of GPUs as well, using the needed amount of
unused machines. This makes the infrastructure flexible and power efficient.
Our pilot infrastructure consist of a high performance cluster and 28
workstations with dual-core CPUs and dedicated graphics cards. Altogether we
can use 10,752 CUDA cores through the network.
\end{document}
% clmomu01.ind
%-----------------------------------------------------------------------
% CLMoMu01 1.0: LaTeX style files for books
% Sample index file for User's guide
% (c) Springer-Verlag HD
%-----------------------------------------------------------------------
\begin{theindex}
\item Absorption\idxquad 327
\item Absorption of radiation \idxquad 289--292,\, 299,\,300
\item Actinides \idxquad 244
\item Aharonov-Bohm effect\idxquad 142--146
\item Angular momentum\idxquad 101--112
\subitem algebraic treatment\idxquad 391--396
\item Angular momentum addition\idxquad 185--193
\item Angular momentum commutation relations\idxquad 101
\item Angular momentum quantization\idxquad 9--10,\,104--106
\item Angular momentum states\idxquad 107,\,321,\,391--396
\item Antiquark\idxquad 83
\item $\alpha$-rays\idxquad 101--103
\item Atomic theory\idxquad 8--10,\,219--249,\,327
\item Average value\newline ({\it see also\/} Expectation value)
15--16,\,25,\,34,\,37,\,357
\indexspace
\item Baker-Hausdorff formula\idxquad 23
\item Balmer formula\idxquad 8
\item Balmer series\idxquad 125
\item Baryon\idxquad 220,\,224
\item Basis\idxquad 98
\item Basis system\idxquad 164,\,376
\item Bell inequality\idxquad 379--381,\,382
\item Bessel functions\idxquad 201,\,313,\,337
\subitem spherical\idxquad 304--306,\, 309,\, 313--314,\,322
\item Bound state\idxquad 73--74,\,78--79,\,116--118,\,202,\, 267,\,
273,\,306,\,348,\,351
\item Boundary conditions\idxquad 59,\, 70
\item Bra\idxquad 159
\item Breit-Wigner formula\idxquad 80,\,84,\,332
\item Brillouin-Wigner perturbation theory\idxquad 203
\indexspace
\item Cathode rays\idxquad 8
\item Causality\idxquad 357--359
\item Center-of-mass frame\idxquad 232,\,274,\,338
\item Central potential\idxquad 113--135,\,303--314
\item Centrifugal potential\idxquad 115--116,\,323
\item Characteristic function\idxquad 33
\item Clebsch-Gordan coefficients\idxquad 191--193
\item Cold emission\idxquad 88
\item Combination principle, Ritz's\idxquad 124
\item Commutation relations\idxquad 27,\,44,\,353,\,391
\item Commutator\idxquad 21--22,\,27,\,44,\,344
\item Compatibility of measurements\idxquad 99
\item Complete orthonormal set\idxquad 31,\,40,\,160,\,360
\item Complete orthonormal system, {\it see}\newline
Complete orthonormal set
\item Complete set of observables, {\it see\/} Complete
set of operators
\indexspace
\item Eigenfunction\idxquad 34,\,46,\,344--346
\subitem radial\idxquad 321
\subsubitem calculation\idxquad 322--324
\item EPR argument\idxquad 377--378
\item Exchange term\idxquad 228,\,231,\,237,\,241,\,268,\,272
\indexspace
\item $f$-sum rule\idxquad 302
\item Fermi energy\idxquad 223
\indexspace
\item H$^+_2$ molecule\idxquad 26
\item Half-life\idxquad 65
\item Holzwarth energies\idxquad 68
\end{theindex}
from one.models import *
from django_extensions.management.jobs import QuarterHourlyJob
from django.core.cache import cache
import json
class Job(QuarterHourlyJob):
help = "Update statistics from OpenNebula."
def execute(self):
stat = json.loads(subprocess.check_output(['/opt/webadmin/cloud/miscellaneous/stat/stat_wrap.sh']))
cache.set('cloud_stat', stat)
......@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-03-07 18:16+0100\n"
"POT-Creation-Date: 2013-03-22 10:43+0100\n"
"PO-Revision-Date: 2013-03-07 17:02+0100\n"
"Last-Translator: \n"
"Language-Team: Hungarian <cloud@ik.bme.hu>\n"
......@@ -17,18 +17,17 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"X-Generator: Lokalize 1.4\n"
#: static/script/cloud.js:24 static/script/cloud.min.js:1
#: static/script/cloud.js:24
msgid "Are you sure deleting key?"
msgstr "Biztosan törli a kulcsot?"
#: static/script/cloud.js:24 static/script/cloud.js.c:296
#: static/script/cloud.js:370 static/script/cloud.js.c:623
#: static/script/cloud.min.js:1 static/script/store.js:288
#: static/script/store.min.js:1
#: static/script/cloud.js:24 static/script/cloud.js.c:301
#: static/script/cloud.js:373 static/script/cloud.js.c:600
#: static/script/store.js:288
msgid "Delete"
msgstr "Törlés"
#: static/script/cloud.js:36 static/script/cloud.min.js:1
#: static/script/cloud.js:36
msgid ""
"Are you sure about reseting store credentials?<br /> You will lose your "
"access to your store account on your existing virtual machines!"
......@@ -36,96 +35,92 @@ msgstr ""
"Biztosan újragenerálja az adattár-kulcsait?<br /> El fogja veszteni az "
"adattár-hozzáférést a már futó virtuális gépekből!"
#: static/script/cloud.js:36 static/script/cloud.min.js:1
#: static/script/cloud.js:36
msgid "Reset"
msgstr "Újragenerálás"
#: static/script/cloud.js:75 static/script/cloud.min.js:1
#: static/script/store.js:323 static/script/store.min.js:1
#: static/script/cloud.js:76 static/script/store.js:323
msgid "Rename"
msgstr "Átnevezés"
#: static/script/cloud.js:271 static/script/cloud.min.js:1
#: static/script/store.js:288 static/script/store.min.js:1
#: static/script/cloud.js:276 static/script/store.js:288
msgid "Cancel"
msgstr "Mégsem"
#: static/script/cloud.js:285 static/script/cloud.min.js:1
#: static/script/cloud.js:290
#, c-format
msgid "Are you sure stopping %s?"
msgstr "Biztosan felfüggeszti a következőt: %s?"
#: static/script/cloud.js:286 static/script/cloud.min.js:1
#: static/script/cloud.js:291
msgid "Stop"
msgstr "Felfüggesztés"
#: static/script/cloud.js:295 static/script/cloud.min.js:1
#: static/script/cloud.js:300
#, c-format
msgid "Are you sure deleting %s?"
msgstr "Biztosan törli a következőt: %s?"
#: static/script/cloud.js:305 static/script/cloud.min.js:1
#: static/script/cloud.js:310
#, c-format
msgid "Are you sure restarting %s?"
msgstr "Biztosan újraindítja a következőt: %s?"
#: static/script/cloud.js:306 static/script/cloud.min.js:1
#: static/script/cloud.js:311
msgid "Restart"
msgstr "Újraindítás"
#: static/script/cloud.js:369 static/script/cloud.min.js:1
#: static/script/cloud.js:372
#, c-format
msgid "Are you sure deleting this %s template?"
msgstr "Biztosan törli a következő sablont: %s?"
#: static/script/cloud.js:547 static/script/cloud.js.c:550
#: static/script/cloud.min.js:1
#: static/script/cloud.js:551 static/script/cloud.js.c:554
msgid "Add owner"
msgstr "Tulajdonos hozzáadása"
#: static/script/cloud.js:550 static/script/cloud.min.js:1
#: static/script/cloud.js:554
msgid "Unknown"
msgstr "Ismeretlen"
#: static/script/cloud.js:623 static/script/cloud.min.js:1
#: static/script/cloud.js:600
#, c-format
msgid "Are you sure deleting <strong>%s</strong>"
msgstr "Törli a következő fájlt: <strong>%s</strong>"
#: static/script/store.js:52 static/script/store.js.c:61
#: static/script/store.js:70 static/script/store.js.c:220
#: static/script/store.js:282 static/script/store.min.js:1
#: static/script/store.js:282
msgid "file"
msgstr "fájl"
#: static/script/store.js:125 static/script/store.min.js:1
#: static/script/store.js:125
msgid "Toplist"
msgstr "Legújabb fájlok"
#: static/script/store.js:127 static/script/store.min.js:1
#: static/script/store.js:127
msgid "Back to the root folder"
msgstr "Vissza a gyökérmappába"
#: static/script/store.js:283 static/script/store.min.js:1
#: static/script/store.js:283
#, c-format
msgid "You are removing the file <strong>%s</strong>."
msgstr "Törli a következő fájlt: <strong>%s</strong>."
#: static/script/store.js:285 static/script/store.min.js:1
#: static/script/store.js:285
#, c-format
msgid "You are removing the folder <strong>%s</strong> (and its content)."
msgstr "Törli a következő könyvtárat és tartalmát: <strong>%s</strong>."
#: static/script/store.js:288 static/script/store.min.js:1
#: static/script/store.js:288
msgid "Are you sure?"
msgstr "Biztos benne?"
#: static/script/store.js:446 static/script/store.js.c:448
#: static/script/store.min.js:1
msgid "Upload"
msgstr "Feltöltés"
#: static/script/store.js:448 static/script/store.min.js:1
#: static/script/store.js:448
msgid "done, processing..."
msgstr "kész, feldolgozás..."
......
......@@ -18,6 +18,7 @@ from firewall.models import Host, Rule, Vlan, Record
from school.models import Person, Group
from store.api import StoreApi
from .util import keygen
from cloud.settings import CLOUD_URL
logger = logging.getLogger(__name__)
pwgen = User.objects.make_random_password
......@@ -487,7 +488,8 @@ class Instance(models.Model):
"""Submit a new instance to OpenNebula."""
from django.template.defaultfilters import escape
out = ""
inst = Instance(pw=pwgen(), template=template, owner=owner, share=share)
inst = Instance(pw=pwgen(), template=template, owner=owner,
share=share, state='PENDING')
inst.save()
hostname = u"cloud-%d" % (inst.id, )
with tempfile.NamedTemporaryFile(delete=False) as f:
......@@ -529,7 +531,7 @@ class Instance(models.Model):
"smbpw": escape(details.smb_password),
"sshkey": escape(details.ssh_private_key),
"neptun": escape(owner.username),
"booturl": "https://cloud.ik.bme.hu/b/%s/" % token,
"booturl": "%sb/%s/" % ( CLOUD_URL, token ),
"extra": extra}
f.write(tpl)
f.close()
......@@ -551,7 +553,6 @@ class Instance(models.Model):
{'neptun': owner.username, 'template': template.name,
'id': inst.one_id})
inst.save()
inst.update_state()
host = Host(vlan=Vlan.objects.get(name=template.network.name),
owner=owner)
host.hostname = hostname
......@@ -618,17 +619,23 @@ class Instance(models.Model):
def _change_state(self, new_state):
"""Change host state in OpenNebula."""
self._update_vm("<STATE>" + new_state + "</STATE>")
self.waiting = True
self.save()
def stop(self):
self._change_state("STOPPED")
self.waiting = True
self.save()
def resume(self):
self._change_state("RESUME")
def poweroff(self):
self._change_state("POWEROFF")
def restart(self):
self._change_state("RESET")
self.waiting = False
self.save()
def renew(self, which='both'):
if which in ['suspend', 'both']:
self.time_of_suspend = self.share.get_type()['suspendx']
......@@ -637,12 +644,12 @@ class Instance(models.Model):
if not (which in ['suspend', 'delete', 'both']):
raise ValueError('No such expiration type.')
self.save()
def save_as(self):
"""Save image and shut down."""
imgname = "template-%d-%d" % (self.template.id, self.id)
self._update_vm('<DISK id="0"><SAVE_AS name="%s"/></DISK>' % imgname)
self._change_state("SHUTDOWN")
self.waiting = True
self.save()
t = self.template
t.state = 'SAVING'
......
......@@ -15,7 +15,7 @@ $(function() {
$('.delete-template').click(function(e) {
e.preventDefault();
e.stopPropagation();
delete_template_confirm($(this).data('id'), $(this).data('name'));
delete_template_confirm($(this).data('url'), $(this).data('id'), $(this).data('name'));
});
$('.delete-key').click(function(e) {
var id = $(this).data('id');
......@@ -25,7 +25,7 @@ $(function() {
$.ajax({
'type': 'POST',
'data': 'id=' + id,
'url': '/ajax/key/delete/',
'url': $(this).data('url'),
'success': function() {
$('#key-' + id).slideUp(700);
}
......@@ -36,7 +36,7 @@ $(function() {
vm_confirm_popup(gettext('Are you sure about reseting store credentials?<br /> You will lose your access to your store account on your existing virtual machines!'), gettext('Reset'), function() {
$.ajax({
type: 'POST',
url: '/ajax/key/reset/',
url: $(this).data('url'),
success: function() {
window.location.reload();
}
......@@ -63,6 +63,7 @@ $(function() {
var oldName = $(this).data('name');
var content = $('#vm-' + id + '-name').html();
var self=this;
var url = $(this).data('url');
$(this).unbind('click').click(function(e){
e.preventDefault();
e.stopPropagation();
......@@ -72,7 +73,7 @@ $(function() {
})
$('#vm-' + id + '-name-details').hide();
$('#vm-' + id + '-name').html('<input type="text" value="' + oldName + '" />\
<input type="submit" value="' + gettext('Rename') + '" />');
<input type="submit" value="' + gettext('Rename') + '" data-url="'+url+'"/>');
$('#vm-' + id + '-name').find('input[type="text"]').click(function(f) {
f.preventDefault();
f.stopPropagation();
......@@ -85,7 +86,7 @@ $(function() {
type: 'POST',
data: 'name=' + newName,
dataType: 'json',
url: '/ajax/vm/rename/' + id + '/',
url: $(this).data('url'),
success: function(data) {
$('#vm-' + id + '-name-details').removeAttr('style');
$('#vm-' + id + '-name').text(data.name);
......@@ -97,7 +98,7 @@ $(function() {
$('.try-template').click(function(e) {
e.preventDefault();
e.stopPropagation();
new_vm($(this).data('id'));
new_vm($(this).data('url'));
});
$('.stop-vm').click(function(e) {
e.preventDefault();
......@@ -135,7 +136,7 @@ $(function() {
$('#modal-container .entry .summary').click(toggleDetails);
});
$('#new-template-button').click(function() {
$.get('/ajax/templateWizard', function(data) {
$.get($(this).data('url'), function(data) {
$('#modal-container').html(data);
})
$('#modal').show();
......@@ -146,7 +147,7 @@ $(function() {
var id=$(this).data('id');
$.ajax({
type: 'GET',
url: '/ajax/templateEditWizard/'+id+'/',
url: $(this).data('url'),
success: function(data){
$('#modal').show();
$('#modal-container').html(data);
......@@ -192,7 +193,11 @@ $(function() {
$('#modal-container').html(content);
$('#shadow').click(function() {
$('#new-group-wizard').html(content);
})
});
$('#modal .prev').click(function() {
$('#modal').hide();
$('#new-group-wizard').html(content);
});
function updateSummary() {
$('#new-group-summary-name').html($('#new-group-name').val());
......@@ -210,8 +215,8 @@ $(function() {
if ($(this).attr('type') == 'password') {
$(this).attr('type', 'text');
$(this).addClass('shown');
this.select();
} else if (this.selectionStart - this.selectionEnd == 0) {
$(this).attr('type', 'password');
$(this).removeClass('shown');
}
......@@ -235,7 +240,7 @@ $(function() {
e.stopPropagation();
$.ajax({
type: 'GET',
url: '/ajax/shareEdit/' + $(this).data('id') + '/',
url: $(this).data('url'),
success: function(data) {
$('#modal').show();
$('#modal-container').html(data);
......@@ -349,13 +354,11 @@ $(function() {
* New VM
*/
function new_vm(template_id) {
function new_vm(url) {
$.ajax({
type: 'POST',
url: '/ajax/vm/new/' + template_id + '/',
url: url,
success: function(data, b, xhrRequest) {
window.location.href = '/'; //xhrRequest.getResponseHeader("Location");
//alert(xhrRequest.getResponseHeader("Location"));
window.location.href = xhrRequest.getResponseHeader("Location");
}
})
......@@ -365,7 +368,7 @@ $(function() {
* Template delete
*/
function delete_template_confirm(id, name) {
function delete_template_confirm(url, id, name) {
confirm_message = interpolate(gettext("Are you sure deleting this %s template?"), ["<strong>" + name + "</strong>"])
vm_confirm_popup(confirm_message, gettext("Delete"), function() {
delete_template(id)
......@@ -375,10 +378,10 @@ $(function() {
* Template delete
*/
function delete_template(id) {
function delete_template(url, id) {
$.ajax({
type: 'POST',
url: '/ajax/template/delete/',
url: url,
data: 'id=' + id,
dataType: 'json',
statusCode: {
......@@ -508,7 +511,7 @@ $(function() {
var neptun = $(this).prev().val();
$.ajax({
type: 'POST',
url: '/ajax/group/' + $(this).data('id') + '/add/',
url: $(this).data('url'),
data: 'neptun=' + neptun,
dataType: 'json',
success: function(data) {
......@@ -530,13 +533,14 @@ $(function() {
var timer;
return function(e) {
var val = $(this).val().split(' ')[0];
var that = this;
clearTimeout(timer);
timer = setTimeout(function() {
if (val.length < 1) return;
$.ajax({
type: 'POST',
data: 'q=' + val,
url: '/ajax/group/autocomplete/',
url: $(that).data('url'),
dataType: 'json',
success: function(data) {
console.log(data);
......@@ -571,21 +575,6 @@ $(function() {
e.stopPropagation();
}
}());
$('#new-owner-form input[type=submit]').click(function() {
var neptun = $(this).prev().val();
$.ajax({
type: 'POST',
url: '/ajax/group/' + $(this).data('id') + '/add/',
data: 'neptun=' + neptun,
dataType: 'json',
success: function(data) {
window.location.reload();
}
}).error(function(data) {
//TODO: fancy modal alert
alert(JSON.parse(data.responseText).status);
})
});
$('#group-members .remove').click(function(e) {
e.preventDefault();
......@@ -593,37 +582,25 @@ $(function() {
var neptun = $(this).data('neptun');
$.ajax({
type: 'POST',
url: '/ajax/group/' + $(this).data('gid') + '/remove/',
url: $(this).data('url'),
data: 'neptun=' + neptun,
success: function(data) {
$('#member-' + neptun).slideUp(700);
}
});
});
/*$('#group-owners .remove').click(function(e) {
e.preventDefault();
e.stopPropagation();
var neptun = $(this).data('neptun');
$.ajax({
type: 'POST',
url: '/ajax/group/' + $(this).data('gid') + '/remove/',
data: 'neptun=' + neptun,
success: function(data) {
$('#member-' + neptun).slideUp(700);
}
});
});*/
$('#groups .delete').click(function(e) {
e.preventDefault();
e.stopPropagation();
var gid = $(this).data('id');
var name = $(this).data('name');
var url = $(this).data('url');
vm_confirm_popup(
interpolate(
gettext('Are you sure deleting <strong>%s</strong>'), [name]), gettext('Delete'), function() {
$.ajax({
type: 'POST',
url: '/ajax/group/delete/',
url: url,
data: 'gid=' + gid,
success: function() {
$('#group-' + gid).slideUp(700);
......
......@@ -12,12 +12,12 @@ body
background-position:80px 0;
margin:0;
padding:0;
overflow: scroll;
overflow-y: scroll;
}
#content
{
width:970px;
width:80%;
text-align:left;
margin:0 auto;
padding-bottom: 100px;
......@@ -62,6 +62,9 @@ body
color: #aaa;
}
}
img {
max-width: 100%;
}
}
.big {
font-size: 2em;
......@@ -287,6 +290,7 @@ body > footer {
box-shadow:0 0 30px rgba(0,0,0,0.4);
margin:0;
background-color: white;
height: 30px;
}
#http-error {
......@@ -299,6 +303,138 @@ body > footer {
padding: 20px;
}
.irasmu p {
margin-top: 20px;
.irasmu {
line-height: 1.8em;
section {
border-radius: 4px;
border: 1px solid #888;
background: #ccc;
box-shadow: 0 0 20px rgba(0,0,0,0.2);
padding: 20px;
margin: 10px;
&.teacher {
border-left: 3px solid #0b4599;
}
}
p {
margin-bottom: 10px;
&:last-child {
margin-bottom: 0px;
}
&.desc {
margin-left: 2em;
}
}
p.teacher {
background: #ddd;
padding: 20px;
border-left-width: 3px;
border-left-style: solid;
border-left-color: #0b4599;
}
p.teacher + p.teacher {
margin-top: 0;
padding-top: 0;
}
h4 {
margin: 0;
padding: 0;
font-size: 1em;
float: left;
}
h4:after {
content: " ♦ ";
}
h4 + p {
margin-top: 0;
}
h2 {
background-color:#000;
background-image:url(/static/image/hexabar.png);
background-position: right center;
background-repeat:no-repeat;
color:#eee;
font-size:1.5em;
border-radius: 4px;
border: 1px solid #888;
box-shadow: 0 0 20px rgba(0,0,0,0.2);
padding: 10px;
margin: 10px;
margin-bottom: 0;
border-bottom-style: none;
}
h2 + section {
border-radius: 0 0 4px 4px;
border-top-style: none;
margin-top: 0;
}
p.figure {
text-align: center;
&.figure-small img {
height: 200px;
transition: 1s 300ms;
-webkit-transition: 1s 300ms;
-moz-transition: 1s 300ms;
-o-transition: 1s 300ms;
-ms-transition: 1s 300ms;
transform-origin: center center;
-webkit-transform-origin: center center;
-moz-transform-origin: center center;
-ms-transform-origin: center center;
-o-transform-origin: center center;
&:hover {
height: 390px;
}
}
}
ol, ul {
margin-left: 1em;
}
nav {
background: #ddd;
padding: 10px;
border-left-width: 3px;
border-left-style: solid;
border-left-color: #ff6;
line-height: 1.2em;
li {
list-style-type: none;
ul li {
list-style-type: square;
margin-left: 1em;
}
}
}
em {
white-space: nowrap;
}
strong {
background-image: url(/static/image/hilite.png);
background-repeat: repeat;
font-style: normal;
font-weight: normal;
padding: 0 .4em;
margin-bottom: 2px;
}
}
@media (max-width: 900px) {
#content {
width: 100%;
}
.boxes {
width: 100% !important;
}
#modal-container{
width: 90%;
left: 0 !important;
margin-left: 2% !important;
}
.contentblock {
margin:5px;
}
body {
font-size: 1.1em;
}
}
.boxes {
width:480px;
width:50%;
float:left;
}
......@@ -300,7 +300,6 @@
}
.value {
float: right;
width: 200px;
text-align: right;
}
.description {
......@@ -309,7 +308,7 @@
.value {
font-size: 0.8em;
word-spacing: 3px;
width: 350px;
max-width: 200px;
}
}
}
......@@ -317,7 +316,7 @@
background-image: url(/static/icons/computer--plus.png);
}
}
#new-share .type-summary, .share-type .value {
#modal, #new-share .type-summary, .share-type .value {
&.type-summary {
font-size: .8em;
text-align: right;
......@@ -630,11 +629,12 @@ table {
.boxhelp {
position: relative;
font-weight: normal;
.boxhelp-box {
color:#000;
position: absolute;
left: -100px;
width: 500px;
max-width: 100%;
z-index: 1000000;
font-size: .7em;
background-color: #ffc;
......@@ -643,6 +643,7 @@ table {
box-shadow:0 0 30px rgba(0,0,0,0.3);
margin:20px;
display: none;
font-size: 14px;
}
.help:hover .boxhelp-box {
display: block;
......
......@@ -4,6 +4,11 @@
width: 100%;
height: 100%;
z-index: 999;
p,dl
{
margin:0;
padding:5px;
}
}
#shadow{
position: fixed;
......@@ -27,7 +32,6 @@
max-height: 60%;
overflow: auto;
.container{
max-height: 400px;
overflow: auto;
border-radius: 2px;
border: 1px solid #888;
......
......@@ -5,6 +5,7 @@ import os
import sys
import time
from django.core.mail import send_mail
from one.models import Instance
logger = logging.getLogger(__name__)
......@@ -12,3 +13,17 @@ class SendMailTask(Task):
def run(self, to, subject, msg, sender=u'noreply@cloud.ik.bme.hu'):
send_mail(subject, msg, sender, [ to ], fail_silently=False)
logger.info("[django][one][tasks.py] %s->%s [%s]" % (sender, to, subject) )
class UpdateInstanceStateTask(Task):
def run(self, one_id):
print one_id
try:
inst = Instance.objects.get(one_id=one_id)
except:
print 'nincs ilyen'
return
inst.update_state()
inst.waiting = False
inst.save()
print inst.state
<!DOCTYPE html>
{% load i18n %}
{% load l10n %}
{% load staticfiles %}
{% get_current_language as lang %}
<html lang="{{lang}}">
......@@ -12,9 +13,11 @@
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script src="{% static "script/jquery.min.js" %}"></script>
<script type="text/javascript" src="{% url django.views.i18n.javascript_catalog %}"></script>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript">
{% if DEBUG %}window.localStorage.removeItem('https://cloud.ik.bme.hu/static/style/style.less:timestamp');{% endif %}
var current_user={{user.id}};
google.load('visualization', '1.0', {'packages':['corechart']});
</script>
{% if DEBUG %}<script src="{% static "script/less.min.js" %}"></script>{% endif %}
<script src="{% static "script/knockout.min.js" %}"></script>
......@@ -26,6 +29,7 @@
{% endif %}
{{ form.media }}
{% block js %}{% endblock %}
<meta name="viewport" content="target-densitydpi=device-dpi, initial-scale=1.0" />
</head>
<body>
<div id="header">
......@@ -70,16 +74,74 @@
<div class="clear"></div>
</div>
<footer>
<div>
<div style="float: left" id="chart_cpu_div"></div>
<a href="/sites/legal/">{% trans "Legal notice" %}</a> |
<a href="/sites/policy/">{% trans "Policy" %}</a> |
<a href="/sites/help/">{% trans "Help" %}</a> |
<a href="/sites/support/">{% trans "Support" %}</a>
<a href="/sites/support/">{% trans "Support" %}</a> |
<a href="/sites/changelog/#{{release}}" title="{% trans "Change log" %}">{{release}}</a>
<div style="float: right" id="chart_mem_div"></div>
</div>
{% if cloud_stat %}
<script type="text/javascript">
google.setOnLoadCallback(drawChart);
function drawChart() {
var data_cpu = new google.visualization.DataTable();
data_cpu.addColumn('string', 'Topping');
data_cpu.addColumn('number', 'Used');
data_cpu.addColumn('number', 'Allocated');
data_cpu.addColumn('number', 'Free');
data_cpu.addRows([
['CPU',
{{cloud_stat.CPU.USED_CPU}},
{{cloud_stat.CPU.ALLOC_CPU}},
{{cloud_stat.CPU.FREE_CPU}},]
]);
var cpu_options = {
'width':400,
'height':20,
isStacked: true,
enableInteractivity: false,
colors: ['red', 'orange', 'blue']
};
var data_mem = new google.visualization.DataTable();
data_mem.addColumn('string', 'Topping');
data_mem.addColumn('number', 'Used');
data_mem.addColumn('number', 'Allocated');
data_mem.addColumn('number', 'Free');
data_mem.addRows([
['RAM',
{{cloud_stat.MEM.USED_MEM|unlocalize}},
{{cloud_stat.MEM.ALLOC_MEM|unlocalize}},
{{cloud_stat.MEM.FREE_MEM|unlocalize}},]
]);
var mem_options = {
'width':400,
'height':20,
isStacked: true,
enableInteractivity: false,
colors: ['red', 'orange', 'blue'],
hAxis : {
viewWindowMode: 'explicit',
viewWindow: {
min: 0,
max: {{cloud_stat.MEM.USED_MEM|unlocalize}}+{{cloud_stat.MEM.ALLOC_MEM|unlocalize}}+{{cloud_stat.MEM.FREE_MEM|unlocalize}}
}
},
};
var chart = new google.visualization.BarChart(document.getElementById('chart_cpu_div'));
chart.draw(data_cpu, cpu_options);
var chart = new google.visualization.BarChart(document.getElementById('chart_mem_div'));
chart.draw(data_mem, mem_options);
}
</script>
{% endif %}
</footer>
<div id="modal" style="display: none">
<div id="shadow"></div>
<div id="modal-container">
</div>
<div id="modal-container"></div>
</div>
<script type="text/javascript">
var _gaq = _gaq || [];
......
......@@ -115,7 +115,7 @@
</div>
</li>
<li class="entry small-row key" style="display: none">
<div class="summary" id="reset-key">
<div class="summary" id="reset-key" data-url="{% url one.views.key_ajax_reset %}">
<div class="name">{% trans "Reset key" %}</div>
<div class="clear"></div>
</div>
......
......@@ -15,7 +15,7 @@
{{key}}
</div>
<div class="actions">
<a href="#" class="remove delete-key" data-id="{{key.id}}">
<a href="#" class="remove delete-key" data-url="{% url one.views.key_ajax_delete %}" data-id="{{key.id}}">
<img src="{% static "icons/minus-circle.png" %}" alt="{% trans 'Remove' %}" />
</a>
</div>
......
......@@ -46,7 +46,7 @@
</div>
{% endif %}
{% if not group %}
<li id="new-template-button" class="entry new small-row">
<li id="new-template-button" class="entry new small-row" data-url="{% url one.views.ajax_template_wizard %}">
<div class="summary">
<div class="name">Új Sablon</div>
<div class="clear"></div>
......@@ -65,7 +65,7 @@
<div class="status">{{t.state}}</div>
<div class="actions">
{% if t.state == 'READY' %}
<a href="#" class="try-template-button" data-id="{{t.id}}" title="{% trans "Try" %}">
<a href="#" class="try-template-button" data-url="{% url new_vm_from_template template=t.id %}" title="{% trans "Try" %}">
<img src="{% static "icons/control.png" %}" alt="{% trans "Start" %}"/>
</a>
<a href="#" class="template-share" data-id="{{t.id}}" data-gid="{{group.id}}" title="{% trans "Share" %}">
......
......@@ -17,7 +17,7 @@
<form action="{% url one.views.vm_unshare i.id %}" method="post">
<span title="{{i.name}}">{{i.name|truncatechars:20}}</span>
({{i.get_running}}/{{i.instance_limit}}) {{i.type}}
<a href="#" class="edit" data-id="{{i.id}}">
<a href="#" class="edit" data-url="{% url ajax_share_edit_wizard id=i.id %}">
<img src="{% static "icons/pencil.png" %}" alt="{% trans "Edit" %}" title="{% trans "Edit" %}" />
</a>
{% csrf_token %}
......@@ -47,21 +47,18 @@
{% endblock status %}
{% block actions %}
<a href="#" class="edit-template" data-id="{{ t.id }}" title="{% trans "Edit" %}">
<a href="#" class="edit-template" title="{% trans "Edit" %}" data-url="{% url one.views.ajax_template_edit_wizard id=t.id %}">
<img src="{% static "icons/pencil.png" %}" alt="{% trans "Edit" %}" />
</a>
{% if t.state == 'READY' %}
<a href="#" class="try-template" data-id="{{t.id}}" title="{% trans "Try" %}">
<a href="#" class="try-template" data-url="{% url one.views.vm_new_ajax template=t.id %}" title="{% trans "Try" %}">
<img src="{% static "icons/control.png" %}" alt="{% trans "Start" %}"/>
</a>
<!--<a href="#" title="{% trans "Edit" %}">
<img src="{% static "icons/pencil.png" %}" alt="{% trans "Edit" %}" />
</a>-->
<a href="#" class="template-share" data-id="{{t.id}}" data-gid="{{group.id}}" title="{% trans "Share" %}">
<img src="{% static "icons/user-share.png" %}" alt="{% trans "Share" %}" />
</a>
{% endif %}
<a href="#" class="delete-template" data-id="{{ t.id }}" data-name="{{ t.name }}" title="{% trans "Remove" %}">
<a href="#" class="delete-template" data-url="{% url one.views.ajax_template_delete %}" data-id="{{ t.id }}" data-name="{{ t.name }}" title="{% trans "Remove" %}">
<img src="{% static "icons/minus-circle.png" %}" alt="{% trans "Remove" %}" />
</a>
{% endblock actions %}
......@@ -23,7 +23,7 @@
{% endblock status %}
{% block actions %}
<a href="#" class="rename-vm" data-name="{{ vm.name }}" data-id="{{ vm.id }}" title="{% trans "Edit name" %}">
<a href="#" class="rename-vm" data-name="{{ vm.name }}" data-id="{{ vm.id }}" title="{% trans "Edit name" %}" data-url="{% url one.views.vm_ajax_rename iid=vm.id %}">
<img src="{% static "icons/pencil.png" %}" alt="{% trans "Edit name" %}" />
</a>
{% if vm.waiting %}
......
......@@ -4,15 +4,17 @@
{% block js %}
<script type="text/javascript">
{% if booting or state != 'ACTIVE' %}
{% if booting or state == 'PENDING' or i.waiting %}
var timer=setInterval(function(){
$.ajax({
type: 'GET',
dataType: 'json',
url: '{% url one.views.vm_ajax_instance_status id %}',
success: function(data){
if (!data.booting && data.state == 'ACTIVE'){
if (!data.waiting && !data.booting && data.state != 'PENDING'){
window.location.reload();
} else if (!data.waiting) {
// window.location.reload();
}
}
});
......@@ -84,14 +86,20 @@
<img src="{% static "image/load.gif" %}" />
{% trans "Saving..." %}
</p>
{% elif state == "ACTIVE" and not booting %}
{% elif state == "ACTIVE" and not booting and not i.waiting %}
<p id="connect" style="display:block; font-size:25px; line-height:2em;text-align:center;">
<a href="{{uri}}" class="button" onclick="return connectbutton();">
<img src="{% static "image/load.gif" %}" id="connecting" style="display:none;" />
{% trans "Running" %}
</a>
</p>
{% elif state == "STOPPED" %}
{% elif state == "ACTIVE" and not booting and i.waiting %}
<p style="display:block; font-size:25px; line-height:2em;text-align:center;">
{% trans "Stopping..." %}
</p>
{% elif state == "STOPPED" and i.waiting %}
<p style="font-size:25px; line-height:2em;text-align:center;">{% trans "Starting..." %}</p>
{% elif state == "STOPPED"%}
<p style="font-size:25px; line-height:2em;text-align:center;">{% trans "Stopped" %}</p>
{% endif %}
{% if state == "DONE" %}
......
{% load l10n %}
<html>
<head>
<!--Load the AJAX API-->
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript">
// Load the Visualization API and the piechart package.
google.load('visualization', '1.0', {'packages':['corechart']});
// Set a callback to run when the Google Visualization API is loaded.
google.setOnLoadCallback(drawChart);
// Callback that creates and populates a data table,
// instantiates the pie chart, passes in the data and
// draws it.
function drawChart() {
// Create the data table.
var data_cpu = new google.visualization.DataTable();
data_cpu.addColumn('string', 'Topping');
data_cpu.addColumn('number', 'Slices');
data_cpu.addColumn('number', 'Free');
data_cpu.addColumn('number', 'Allocated');
data_cpu.addColumn('number', 'Used');
data_cpu.addRows([
['Free CPU', {{STAT.CPU.FREE_CPU}}],
['Allocated CPU', {{STAT.CPU.ALLOC_CPU}}],
['Used CPU', {{STAT.CPU.USED_CPU}}],
['CPU %', {{STAT.CPU.FREE_CPU}},{{STAT.CPU.ALLOC_CPU}},{{STAT.CPU.USED_CPU}}],
]);
// Set chart options
var cpu_options = {'title':'Cloud CPU usage percent (100/CPU)',
'width':400,
'height':300,
slices: {0: {color: 'blue'}, 1:{color: 'orange'},
2:{color: 'red'}}
var cpu_options = {
'title':'Cloud CPU usage percent (100/CPU)',
'width':900,
'height':50,
isStacked: true
};
var data_mem = new google.visualization.DataTable();
data_mem.addColumn('string', 'Topping');
data_mem.addColumn('number', 'Slices');
......@@ -42,27 +28,24 @@
['Allocated Memory', {{STAT.MEM.ALLOC_MEM|unlocalize}}],
['Used Memory', {{STAT.MEM.USED_MEM|unlocalize}}],
]);
// Set chart options
var mem_options = {'title':'Cloud Memory usage in {{STAT.DIMENSION}}',
var mem_options = {
'title':'Cloud Memory usage in {{STAT.DIMENSION}}',
'width':400,
'height':300,
slices: {0: {color: 'blue'}, 1:{color: 'orange'},
2:{color: 'red'}}
slices: {
0: {color: 'blue'},
1:{color: 'orange'},
2:{color: 'red'}
},
};
// Instantiate and draw our chart, passing in some options.
var chart = new
google.visualization.PieChart(document.getElementById('chart_cpu_div'));
var chart = new google.visualization.BarChart(document.getElementById('chart_cpu_div'));
chart.draw(data_cpu, cpu_options);
var chart = new
google.visualization.PieChart(document.getElementById('chart_mem_div'));
var chart = new google.visualization.PieChart(document.getElementById('chart_mem_div'));
chart.draw(data_mem, mem_options);
}
</script>
</head>
<body>
<!--Div that will hold the pie chart-->
<div>Running VMs: {{STAT.VMS}}</div>
<div id="chart_cpu_div"></div>
<div id="chart_mem_div"></div>
......
......@@ -32,8 +32,6 @@ logger = logging.getLogger(__name__)
def _list_instances(request):
instances = Instance.objects.exclude(state='DONE').filter(owner=request.user)
for i in instances:
i.update_state()
instances = instances.exclude(state='DONE')
return instances
......@@ -335,6 +333,7 @@ def vm_new(request, template=None, share=None, redir=True):
messages.error(request, _('Failed to create virtual machine.'))
inst = None
if inst:
inst.waiting = True
inst.time_of_suspend = time_of_suspend
inst.time_of_delete = time_of_delete
inst.save()
......@@ -356,9 +355,6 @@ vm_list = login_required(VmListView.as_view())
@login_required
def vm_show(request, iid):
inst = get_object_or_404(Instance, id=iid, owner=request.user)
inst.update_state()
if inst.template.state == "SAVING":
inst.check_if_is_save_as_done()
try:
ports = inst.firewall_host.list_ports()
except:
......@@ -388,10 +384,10 @@ def vm_show(request, iid):
@login_required
def vm_ajax_instance_status(request, iid):
inst = get_object_or_404(Instance, id=iid, owner=request.user)
inst.update_state()
return HttpResponse(json.dumps({
'booting': not inst.active_since,
'state': inst.state,
'waiting': inst.waiting,
'template': {
'state': inst.template.state
}}))
......@@ -612,7 +608,7 @@ def stat(request):
)))
def sites(request, site):
if site in [ "legal", "policy", "help", "support" ]:
if site in [ "legal", "policy", "help", "support", "changelog", ]:
return render_to_response("sites/%s.html" % site, RequestContext(request, {}))
else:
return redirect(home)
......
......@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-03-07 18:16+0100\n"
"POT-Creation-Date: 2013-03-22 10:43+0100\n"
"PO-Revision-Date: 2013-03-07 17:48+0100\n"
"Last-Translator: \n"
"Language-Team: American English <cloud@ik.bme.hu>\n"
......@@ -17,112 +17,108 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"X-Generator: Lokalize 1.4\n"
#: models.py:12
#: models.py:13
msgid "Hungarian"
msgstr "Magyar"
#: models.py:12
#: models.py:13
msgid "English"
msgstr "Angol"
#: models.py:28
#: models.py:46
msgid "language"
msgstr "nyelv"
#: models.py:30
#: models.py:48
msgid "code"
msgstr "kód"
#: models.py:53
#: models.py:73
#, python-format
msgid "%(first)s %(last)s"
msgstr "%(last)s %(first)s"
#: models.py:59
#: models.py:82
msgid "person"
msgstr "személy"
#: models.py:60
#: models.py:83
msgid "persons"
msgstr "személyek"
#: models.py:64
#: models.py:87
msgid "course code"
msgstr "tárgykód"
#: models.py:66 models.py:68 models.py:120 models.py:146
#: models.py:89 models.py:91 models.py:143 models.py:169
msgid "name"
msgstr "név"
#: models.py:70
#: models.py:93
msgid "default group"
msgstr "alapértelmezett csoport"
#: models.py:71
msgid "New users will automatically get to this group."
#: models.py:94
msgid "New users will be automatically assigned to this group."
msgstr "Az új hallgatók automatikusan ebbe a csoportba kerülnek."
#: models.py:73 models.py:115 models.py:149 models.py:162
#: models.py:96 models.py:138 models.py:175 models.py:189
msgid "owners"
msgstr "tulajdonosok"
#: models.py:76 models.py:147
#: models.py:99 models.py:171
msgid "course"
msgstr "tárgy"
#: models.py:77
#: models.py:100
msgid "courses"
msgstr "tárgyak"
#: models.py:83
#: models.py:106
#, python-format
msgid "%s (auto)"
msgstr "%s (auto)"
#: models.py:108
#: models.py:131
msgid "short name"
msgstr "rövid név"
#: models.py:114
#: models.py:137 models.py:188
msgid "(none)"
msgstr "(nincs)"
#: models.py:121
#: models.py:144
msgid "start"
msgstr "kezdet"
#: models.py:122
#: models.py:145
msgid "end"
msgstr "vége"
#: models.py:125 models.py:148
#: models.py:148 models.py:173
msgid "semester"
msgstr "félév"
#: models.py:126
#: models.py:149
msgid "semesters"
msgstr "félévek"
#: models.py:139
#: models.py:162
msgid "There is no current semester."
msgstr "Nincs aktuális félév."
#: models.py:150
#: models.py:177
msgid "members"
msgstr "tagok"
#: models.py:154
#: models.py:181
msgid "group"
msgstr "csoport"
#: models.py:155
#: models.py:182
msgid "groups"
msgstr "csoportok"
#: models.py:161
msgid "n/a"
msgstr "n/a"
#: views.py:41
msgid "EduID is not available."
msgstr "Az EduID nem elérhető."
......@@ -147,15 +143,15 @@ msgstr "„%s” tárgy oktatóihoz felvéve."
msgid "Failed to add course \"%s\" ownership."
msgstr "„%s” tárgy oktatóihoz felvétel sikertelen."
#: views.py:148
#: views.py:150
msgid "Could not found Person object."
msgstr "Nem található Személy objektum."
#: views.py:187
#: views.py:189
msgid "Invalid NEPTUN code found."
msgstr "Érvénytelen Neptun-kód."
#: views.py:208 views.py:223 views.py:262
#: views.py:210 views.py:225 views.py:264
msgid "Invalid NEPTUN code"
msgstr "Érvénytelen Neptun-kód"
......@@ -320,6 +316,9 @@ msgstr "Felhasználó hozzáadása"
msgid "User NEPTUN code"
msgstr "Felhasználó Neptun-kódja"
#~ msgid "n/a"
#~ msgstr "n/a"
#~ msgid "Templates are customized versions of the base images."
#~ msgstr "A sablonok alaprendszerek testre szabott változatai."
......
......@@ -6,34 +6,56 @@ from django.core.exceptions import ValidationError
from datetime import datetime
from django.conf import settings
import one.models
import logging
LANGUAGE_CODE = settings.LANGUAGE_CODE
LANGUAGE_CHOICES = (('hu', _('Hungarian')), ('en', _('English')))
logger = logging.getLogger(__name__)
def create_user_profile(sender, instance, created, **kwargs):
"""
User creation hook.
Ensure that the specified user has an associated profile.
@param sender: The model class.
@type instance: User
@param instance: The user to create a profile for (if necessary).
@type created: Boolean
@param created: True if a new record was created.
"""
if created:
try:
p = Person.objects.get(code=instance.username)
except Exception:
except Person.DoesNotExist:
p = Person.objects.create(code=instance.username)
except:
except Exception as e:
logger.warning("Couldn't create profile for user: %(username)s"
"\nReason: %(exception)s",
{"username": instance.username,
"exception": e})
return
p.code = instance.username
p.clean()
p.save()
post_save.connect(create_user_profile, sender=User)
class Person(models.Model):
user = models.ForeignKey(User, null=True, blank=True, unique=True)
language = models.CharField(verbose_name=_('language'), blank=False, max_length=10,
choices=LANGUAGE_CHOICES, default=LANGUAGE_CODE)
language = models.CharField(verbose_name=_('language'), blank=False,
max_length=10, choices=LANGUAGE_CHOICES, default=LANGUAGE_CODE)
code = models.CharField(_('code'), max_length=30, unique=True)
def get_owned_shares(self):
return one.models.Share.objects.filter(group__in=self.owned_groups.all())
"""Get the shares of the groups which the person owns."""
return one.models.Share.objects.filter(
group__in=self.owned_groups.all())
def get_shares(self):
return one.models.Share.objects.filter(group__in=self.course_groups.all())
"""Get the shares of the groups which the person is a member of."""
return one.models.Share.objects.filter(
group__in=self.course_groups.all())
def short_name(self):
if self.user:
......@@ -45,15 +67,16 @@ class Person(models.Model):
return self.code
def __unicode__(self):
u = self.user
if not u:
return self.code
if u.last_name and u.first_name:
if self.user:
if self.user.last_name and self.user.first_name:
# TRANSLATORS: full name format used in enumerations
return _("%(first)s %(last)s") % {'first': u.first_name,
'last': u.last_name}
return _("%(first)s %(last)s") % {
'first': self.user.first_name,
'last': self.user.last_name}
else:
return u.username
return self.user.username
else:
return self.code
class Meta:
verbose_name = _('person')
......@@ -68,7 +91,7 @@ class Course(models.Model):
verbose_name=_('name'))
default_group = models.ForeignKey('Group', null=True, blank=True,
related_name='default_group_of', verbose_name=_('default group'),
help_text=_('New users will automatically get to this group.'))
help_text=_('New users will be automatically assigned to this group.'))
owners = models.ManyToManyField(Person, blank=True, null=True,
verbose_name=_('owners'))
......@@ -144,10 +167,14 @@ class Semester(models.Model):
class Group(models.Model):
name = models.CharField(max_length=80, verbose_name=_('name'))
course = models.ForeignKey('Course', null=True, blank=True, verbose_name=_('course'))
semester = models.ForeignKey('Semester', null=False, blank=False, verbose_name=_('semester'))
owners = models.ManyToManyField(Person, blank=True, null=True, related_name='owned_groups', verbose_name=_('owners'))
members = models.ManyToManyField(Person, blank=True, null=True, related_name='course_groups', verbose_name=_('members'))
course = models.ForeignKey('Course', null=True, blank=True,
verbose_name=_('course'))
semester = models.ForeignKey('Semester', null=False, blank=False,
verbose_name=_('semester'))
owners = models.ManyToManyField(Person, blank=True, null=True,
related_name='owned_groups', verbose_name=_('owners'))
members = models.ManyToManyField(Person, blank=True, null=True,
related_name='course_groups', verbose_name=_('members'))
class Meta:
unique_together = (('name', 'course', 'semester', ), )
......@@ -155,10 +182,10 @@ class Group(models.Model):
verbose_name_plural = _('groups')
def owner_list(self):
if self.owners:
if self.owners and self.owners.count() > 0:
return ", ".join([p.short_name() for p in self.owners.all()])
else:
return _("n/a")
return _("(none)")
owner_list.verbose_name = _('owners')
def member_count(self):
......@@ -172,4 +199,4 @@ class Group(models.Model):
@models.permalink
def get_absolute_url(self):
return ('group_show', None, {'gid':self.id})
return ('group_show', None, {'gid': self.id})
......@@ -16,7 +16,7 @@
<small class="details">(<a href="{{ group.get_absolute_url }}">{% trans "More details" %}</a>)</small>
</div>
<div class="actions">
<a href="#" class="delete" data-id="{{group.id}}" data-name="{{group.name}}">
<a href="#" class="delete" data-url="{% url school.views.group_ajax_delete %}" data-id="{{group.id}}" data-name="{{group.name}}">
<img src="{% static "icons/minus-circle.png" %}" alt="{% trans "Delete" %}" title="{% trans "Delete" %}" />
</a>
<a href="#" class="hide-group" data-id="{{group.id}}">
......
......@@ -37,7 +37,7 @@
<div class="name">{% trans "Add user" %}</div>
<div id="new-member-form">
<input type="text" placeholder="{% trans "User NEPTUN code" %}" />
<input type="submit" value="{% trans "Add user" %}" data-id="{{group.id}}"/>
<input type="submit" value="{% trans "Add user" %}" data-url="{% url school.views.group_ajax_add_new_member gid=group.id %}"/>
</div>
<div class="clear"></div>
</div>
......
......@@ -19,7 +19,7 @@
{% endif %}
</div>
<div class="actions">
<a href="#" class="remove" data-gid="{{group.id}}" data-neptun="{{member.code}}">
<a href="#" class="remove" data-url="{% url school.views.group_ajax_remove_member gid=group.id %}" data-neptun="{{member.code}}">
<img src="{% static "icons/minus-circle.png" %}" alt="{% trans 'Remove' %}" />
</a>
</div>
......
......@@ -53,7 +53,7 @@
</div>
<div class="details" id="new-owner-form">
<div class="container">
<input type="text" placeholder="{% trans "Owner name/NEPTUN" %}" />
<input type="text" placeholder="{% trans "Owner name/NEPTUN" %}" data-url="{% url school.views.group_ajax_owner_autocomplete %}"/>
<div id="new-owner-autocomplete"></div>
</div>
</div>
......
"""
This file demonstrates writing tests using the unittest module. These will pass
when you run "manage.py test".
from django.test import TestCase
from models import create_user_profile, Person
Replace this with more appropriate tests for your application.
"""
class MockUser:
username = "testuser"
from django.test import TestCase
class CreateUserProfileTestCase(TestCase):
def setUp(self):
self.user = MockUser()
for p in Person.objects.all():
p.delete()
def test_new_profile(self):
"""Test profile creation functionality for new user."""
create_user_profile(self.user.__class__, self.user, True)
self.assertEqual(Person.objects.filter(
code=self.user.username).count(), 1)
def test_existing_profile(self):
"""Test profile creation functionality when it already exists."""
Person.objects.create(code=self.user.username)
create_user_profile(self.user.__class__, self.user, True)
self.assertEqual(Person.objects.filter(
code=self.user.username).count(), 1)
class PersonTestCase(TestCase):
def setUp(self):
self.testperson = Person.objects.create(code='testperson')
def test_language_code_in_choices(self):
"""Test whether the default value for language is a valid choice."""
# TODO
language_field = self.testperson._meta.get_field('language')
choice_codes = [code for (code, _) in language_field.choices]
self.assertIn(language_field.default, choice_codes)
def test_get_owned_shares(self):
# TODO
self.testperson.get_owned_shares()
def test_get_shares(self):
# TODO
self.testperson.get_shares()
def test_short_name(self):
# TODO
self.testperson.short_name()
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.assertEqual(1 + 1, 2)
def test_unicode(self):
# TODO
self.testperson.__unicode__()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment