Permalink
Browse files

added simple terminal

  • Loading branch information...
1 parent 0eaa342 commit f0ee0f9d0bc03a788f7020ad80051ba71aaf4789 SushiTee committed Feb 6, 2013
View
@@ -9,6 +9,7 @@
uptee/celerybeat.pid
uptee/celerybeat-schedule
uptee/settings_local.py
+uptee/cache
uptee/media
tests.json
build/
@@ -0,0 +1,136 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding field 'Moderator.console_allowed'
+ db.add_column('accounts_moderator', 'console_allowed',
+ self.gf('django.db.models.fields.BooleanField')(default=False),
+ keep_default=False)
+
+
+ def backwards(self, orm):
+ # Deleting field 'Moderator.console_allowed'
+ db.delete_column('accounts_moderator', 'console_allowed')
+
+
+ models = {
+ 'accounts.activation': {
+ 'Meta': {'object_name': 'Activation'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'unique': 'True', 'null': 'True'}),
+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'activation'", 'unique': 'True', 'to': "orm['auth.User']"})
+ },
+ 'accounts.moderator': {
+ 'Meta': {'object_name': 'Moderator'},
+ 'allowed_options': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'moderators'", 'symmetrical': 'False', 'to': "orm['mod.Option']"}),
+ 'allowed_tunings': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'moderators'", 'symmetrical': 'False', 'to': "orm['mod.Tune']"}),
+ 'console_allowed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'edit_automatic_restart_allowed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'edit_map_download_allowed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'edit_rcon_commands_allowed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'edit_votes_allowed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'map_upload_allowed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'restart_allowed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'server': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'moderators'", 'to': "orm['mod.Server']"}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'moderators'", 'to': "orm['auth.User']"})
+ },
+ 'accounts.userprofile': {
+ 'Meta': {'object_name': 'UserProfile'},
+ 'allowed_mods': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'users'", 'symmetrical': 'False', 'to': "orm['mod.Mod']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'template': ('django.db.models.fields.CharField', [], {'default': "'new_design'", 'max_length': '100'}),
+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"})
+ },
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'mod.mod': {
+ 'Meta': {'ordering': "['upload_date', 'title']", 'object_name': 'Mod'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'mod_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'upload_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
+ },
+ 'mod.option': {
+ 'Meta': {'ordering': "['id']", 'object_name': 'Option'},
+ 'command': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'server': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'config_options'", 'to': "orm['mod.Server']"}),
+ 'value': ('django.db.models.fields.CharField', [], {'max_length': '500', 'blank': 'True'}),
+ 'widget': ('django.db.models.fields.IntegerField', [], {'default': '1'})
+ },
+ 'mod.port': {
+ 'Meta': {'ordering': "['port']", 'object_name': 'Port'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'port': ('django.db.models.fields.IntegerField', [], {'unique': 'True'})
+ },
+ 'mod.server': {
+ 'Meta': {'ordering': "['owner', 'mod', 'port']", 'object_name': 'Server'},
+ 'automatic_restart': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'description_html': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'map_download_allowed': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'mod': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'servers'", 'to': "orm['mod.Mod']"}),
+ 'online': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'servers'", 'to': "orm['auth.User']"}),
+ 'pid': ('django.db.models.fields.IntegerField', [], {'unique': 'True', 'null': 'True', 'blank': 'True'}),
+ 'port': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'server'", 'unique': 'True', 'null': 'True', 'to': "orm['mod.Port']"}),
+ 'random_key': ('django.db.models.fields.CharField', [], {'max_length': '24', 'unique': 'True', 'null': 'True'}),
+ 'server_info': ('picklefield.fields.PickledObjectField', [], {'null': 'True', 'blank': 'True'}),
+ 'set_online_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
+ },
+ 'mod.tune': {
+ 'Meta': {'ordering': "['id']", 'object_name': 'Tune'},
+ 'command': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'server': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'config_tunes'", 'to': "orm['mod.Server']"}),
+ 'value': ('django.db.models.fields.FloatField', [], {})
+ }
+ }
+
+ complete_apps = ['accounts']
@@ -39,6 +39,7 @@ class Moderator(models.Model):
edit_map_download_allowed = models.BooleanField(default=False)
edit_votes_allowed = models.BooleanField(default=False)
map_upload_allowed = models.BooleanField(default=False)
+ console_allowed = models.BooleanField(default=False)
edit_rcon_commands_allowed = models.BooleanField(default=False)
allowed_options = models.ManyToManyField(Option, related_name='moderators')
allowed_tunings = models.ManyToManyField(Tune, related_name='moderators')
No changes.
View
@@ -0,0 +1,70 @@
+import time
+from telnetlib import Telnet
+from django.core.cache import cache
+
+
+class TelnetClient():
+
+ def __init__(self, port, password, server_id):
+ self.terminate = False
+ self.server_id = server_id
+ self.tn = Telnet('localhost', port)
+ self.tn.read_until('Enter password:\r\n')
+ self.tn.write(password + '\r\n')
+ self.start_time = time.time()
+ self.run()
+
+ def read(self):
+ try:
+ return self.tn.read_eager()
+ except:
+ self.terminate = True
+ return ''
+
+ def write(self, line):
+ try:
+ self.tn.write(line.encode('UTF-8') + '\r\n')
+ except:
+ self.terminate = True
+
+ def send_back(self, lines_to_send):
+ key = 'server-{0}-out'.format(self.server_id)
+ lines = cache.get(key, [])
+ lines.extend(lines_to_send)
+ cache.set(key, lines)
+
+ def get_lines_from_cache(self):
+ key = 'server-{0}-in'.format(self.server_id)
+ lines = cache.get(key, [])
+ cache.delete(key)
+ return lines
+
+ def ping(self):
+ pingtime = cache.get('server-{0}-ping'.format(self.server_id))
+ if not pingtime or time.time() - pingtime > 3:
+ self.terminate = True
+
+ def run(self):
+ received_data = ''
+ while True:
+ # check if we should shut down the process
+ self.ping()
+
+ # terminate!!
+ if self.terminate:
+ break
+
+ # receive lines from server
+ received_data += self.read()
+ lines = received_data.split('\r\n')
+ received_data = lines.pop() # put tail back to buffer for next run
+
+ # lines to send back to client
+ if lines:
+ self.send_back(lines)
+
+ # lines to send to telnet server
+ for line in self.get_lines_from_cache():
+ self.write(line)
+
+ time.sleep(0.2) # be nice to CPU run only 5 times per sec
View
@@ -0,0 +1,19 @@
+import os
+import signal
+import psutil
+from celery import task
+from multiprocessing import Process
+from django.core.cache import cache
+from econ import TelnetClient
+
+
+@task()
+def telnet_client(server_id, port):
+ key = 'server-{0}-pid'.format(server_id)
+ pid = cache.get(key)
+ if pid and pid in psutil.get_pid_list():
+ os.kill(pid, signal.SIGTERM)
+ p = Process(target=TelnetClient, args=(port, 'uptee', server_id))
+ p.start()
+ cache.set(key, p.pid)
+ p.join()
View
@@ -98,3 +98,7 @@ def save(self):
user = self.cleaned_data['user']
moderator = Moderator(server=self.server, user=user)
moderator.save()
+
+
+class CommandForm(forms.Form):
+ command = forms.CharField(max_length=500)
View
@@ -193,6 +193,8 @@ def save_config(self, download=False):
config.add_option('sv_port', '8303')
else:
config.add_option('sv_port', self.port.port)
+ config.add_option('ec_port', self.port.port + 100)
+ config.add_option('ec_password', 'uptee')
for tune in self.config_tunes.all():
config.add_tune(tune.command, tune.value)
for vote in self.config_votes.all():
View
@@ -16,13 +16,16 @@
url(r'^server/(?P<server_id>\d+)/editserverdescription/$', 'server_edit_description', name='server_edit_description'),
url(r'^server/(?P<server_id>\d+)/moderators/$', 'server_moderators', name='server_moderators'),
url(r'^server/(?P<server_id>\d+)/moderators/(?P<user_id>\d+)/$', 'server_edit_moderator', name='server_edit_moderator'),
+ url(r'^server/(?P<server_id>\d+)/console/$', 'server_console', name='server_console'),
url(r'^server/(?P<server_id>\d+)/downloadconfig/$', 'config_download', name='config_download'),
url(r'^startstopserver/(?P<server_id>\d+)/$', 'start_stop_server', name='start_stop_server'),
url(r'^updatesettings/(?P<server_id>\d+)/$', 'update_settings', name='update_settings'),
url(r'^updatevotes/(?P<server_id>\d+)/$', 'update_votes', name='update_votes'),
url(r'^updaterconcommands/(?P<server_id>\d+)/$', 'update_rcon_commands', name='update_rcon_commands'),
url(r'^updatetunings/(?P<server_id>\d+)/$', 'update_tunes', name='update_tunes'),
url(r'^server_info_update/(?P<server_id>\d+)/$', 'server_info_update_ajax', name='server_info_update_ajax'),
+ url(r'^server_terminal_command/(?P<server_id>\d+)/$', 'terminal_command_ajax', name='terminal_command_ajax'),
+ url(r'^server_terminal_receive/(?P<server_id>\d+)/$', 'terminal_receive_ajax', name='terminal_receive_ajax'),
url(r'^map/(?P<map_id>\d+)/$', 'map_details', name='map_details'),
url(r'^map/(?P<map_id>\d+)/download/$', 'map_download', name='map_download'),
url(r'^map/(?P<map_id>\d+)/delete/$', 'delete_map', name='delete_map')
View
@@ -1,15 +1,18 @@
import os
+from time import time
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
+from django.core.cache import cache
from django.core.urlresolvers import reverse
from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404, redirect, render_to_response
from django.template import RequestContext
from django.views.decorators.http import require_POST
from annoying.decorators import ajax_request
-from mod.forms import ChangeModForm, MapUploadForm, ModeratorForm, ServerDescriptionForm
-from mod.models import Map, Mod, Option, RconCommand, Server, Vote
+from econ.tasks import telnet_client
+from mod.forms import CommandForm, ChangeModForm, MapUploadForm, ModeratorForm, ServerDescriptionForm
+from mod.models import Map, Option, RconCommand, Server, Vote
from settings import MEDIA_ROOT
@@ -92,6 +95,10 @@ def server_edit_moderator(request, server_id, user_id):
moderator.edit_map_download_allowed = True
else:
moderator.edit_map_download_allowed = False
+ if 'console_allowed' in request.POST.keys():
+ moderator.console_allowed = True
+ else:
+ moderator.console_allowed = False
if 'edit_votes_allowed' in request.POST.keys():
moderator.edit_votes_allowed = True
else:
@@ -306,6 +313,25 @@ def server_change_mod(request, server_id):
}, context_instance=RequestContext(request))
+@login_required
+def server_console(request, server_id):
+ server = get_object_or_404(Server.active.select_related(), pk=server_id)
+ if not server.is_online:
+ raise Http404
+ moderator = server.moderators.filter(user=request.user)
+ if server.owner != request.user and (not moderator or not moderator[0].console_allowed):
+ raise Http404
+ moderator = moderator[0] if moderator else None
+ cache.delete('server-{0}-in'.format(server_id))
+ cache.delete('server-{0}-out'.format(server_id))
+ cache.set('server-{0}-ping'.format(server_id), time())
+ telnet_client.delay(server_id, server.port.port + 100)
+ return render_to_response('mod/server_detail_console.html', {
+ 'server': server,
+ 'moderator': moderator
+ }, context_instance=RequestContext(request))
+
+
def server_votes(request, server_id):
server = get_object_or_404(Server.active.select_related(), pk=server_id)
moderator = server.moderators.filter(user=request.user) if request.user.is_authenticated() else None
@@ -536,3 +562,33 @@ def server_info_update_ajax(request, server_id):
raise Http404
server = get_object_or_404(Server.active.select_related(), pk=server_id)
return {'server_info': server.server_info}
+
+
+@ajax_request
+@require_POST
+def terminal_command_ajax(request, server_id):
+ server = get_object_or_404(Server.active.select_related(), pk=server_id)
+ if not server.is_online:
+ raise Http404
+ form = CommandForm(request.POST)
+ if not form.is_valid():
+ raise Http404
+ key = 'server-{0}-in'.format(server_id)
+ lines = cache.get(key, [])
+ lines.append(form.cleaned_data['command'])
+ cache.set(key, lines)
+ cache.set('server-{0}-ping'.format(server_id), time())
+ return {}
+
+
+@ajax_request
+def terminal_receive_ajax(request, server_id):
+ server = get_object_or_404(Server.active.select_related(), pk=server_id)
+ if not server.is_online:
+ raise Http404
+ cache.set('server-{0}-ping'.format(server_id), time())
+ key = 'server-{0}-out'.format(server_id)
+ lines = cache.get(key, [])
+ cache.delete(key)
+ cache.delete('server-{0}-terminate'.format(server_id))
+ return {} if not lines else {"lines": lines}
@@ -30,6 +30,13 @@
}
}
+CACHES = {
+ 'default': {
+ 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
+ 'LOCATION': os.path.join(PROJECT_DIR, 'cache'),
+ }
+}
+
# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
@@ -169,6 +176,7 @@
'django.contrib.admin',
'django_pybrowscap',
'lib',
+ 'econ',
'crumbs',
'djcelery',
'pagination',
Oops, something went wrong.

0 comments on commit f0ee0f9

Please sign in to comment.