Commit 38f7e51b authored by Thomas Gambier's avatar Thomas Gambier

runner: totally remove runner directory

webrunner is replaced by Theia

See merge request !108
parent 74c0a347
unreleased
==========
* runner: totally remove runner directory (webrunner is replaced by Theia)
0.129 (2023-01-18)
==================
......
include CHANGES.txt
recursive-include slapos/onetimeupload/templates *.html
recursive-include slapos/runner/templates *.html
recursive-include slapos/runner/static *.css *.png *.js *.gif
......@@ -39,12 +39,10 @@ setup(name=name,
'setuptools', # namespaces
'slapos.core', # as it provides library for slap
'xml_marshaller', # needed to dump information
'GitPython', #needed for git manipulation into slaprunner
'croniter', # needed to know cron schedule
'pytz', # needed to manipulate timezone
'tzlocal', # needed to manipulate timezone
'backports.lzma',
'passlib',
'netifaces',
'erp5.util',
'PyRSS2Gen',
......@@ -105,8 +103,6 @@ setup(name=name,
'backup-identity-script-excluding-path = slapos.resilient.identity_script_excluding_path:calculateSignature',
'securedelete = slapos.securedelete:main',
'slapos-kill = slapos.systool:kill',
'slaprunnertest = slapos.runner.runnertest:main',
'slaprunnerteststandalone = slapos.runner.runnertest:runStandaloneUnitTest',
'zodbpack = slapos.zodbpack:run [zodbpack]',
'networkbench = slapos.networkbench:main',
'cachechecker = slapos.cachechecker:web_checker_utility',
......
# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
try:
__import__('pkg_resources').declare_namespace(__name__)
except ImportError:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
# -*- coding: utf-8 -*-
# vim: set et sts=2:
#pylint: disable-all
import json
from flask import Response
def as_json(f):
def inner(*args, **kwargs):
return Response(json.dumps(f(*args, **kwargs)), mimetype='application/json')
return inner
# -*- coding: utf-8 -*-
# vim: set et sts=2:
# pylint: disable-msg=W0311,C0301,C0103,C0111
import datetime
import hashlib
import os
import re
import shutil
from six.moves.urllib.parse import unquote
import zipfile
import fnmatch
import werkzeug
from slapos.runner.utils import realpath, tail, isText
from slapos.util import str2bytes
class FileBrowser(object):
"""This class contains all base functions for file browser"""
def __init__(self, config):
self.config = config
def _realdir(self, dir):
realdir = realpath(self.config, unquote(dir))
if not realdir:
raise NameError('Could not load directory %s: Permission denied' % dir)
return realdir
def _filterPathList(self, path_list):
"""Filter out paths that matches a list ignored file patterns.
"""
# This could be configurable ...
ignored_file_list = '''
*.py[cod]
*.o
'''
for pattern in ignored_file_list.splitlines():
if pattern.strip():
path_list = [path for path in path_list if not fnmatch.fnmatch(path, pattern)]
return path_list
def listDirs(self, dir, all=False):
"""List elements of directory 'dir' taken"""
html = 'var gsdirs = [], gsfiles = [];'
dir = unquote(dir)
# XXX-Marco 'dir' and 'all' should not shadow builtin names
realdir = realpath(self.config, dir)
if not realdir:
raise NameError('Could not load directory %s: Permission denied' % dir)
ldir = sorted(os.listdir(realdir), key=str.lower)
for f in self._filterPathList(ldir):
if f.startswith('.') and not all: # do not display this file/folder
continue
ff = os.path.join(dir, f)
realfile = os.path.join(realdir, f)
mdate = datetime.datetime.fromtimestamp(os.path.getmtime(realfile)
).strftime("%Y-%d-%m %I:%M")
md5sum = hashlib.md5(str2bytes(realfile)).hexdigest()
if not os.path.isdir(realfile):
size = os.path.getsize(realfile)
regex = re.compile("(^.*)\.(.*)", re.VERBOSE)
ext = regex.sub(r'\2', f)
if ext == f:
ext = "unknow"
else:
ext = str.lower(ext)
html += 'gsfiles.push(new gsItem("1", "%s", "%s", "%s", "%s", "%s", "%s"));' % (f, ff, size, md5sum, ext, mdate)
else:
html += 'gsdirs.push(new gsItem("2", "%s", "%s", "0", "%s", "dir", "%s"));' % (f, ff, md5sum, mdate)
return html
def fancylistDirs(self, dir, key, listfiles, all=False):
dir = unquote(dir)
realdir = realpath(self.config, dir)
if not realdir:
raise NameError('Could not load directory %s: Permission denied' % dir)
fileList = []
dirList = []
i = 0
ldir = sorted(os.listdir(realdir), key=str.lower)
for f in self._filterPathList(ldir):
if f.startswith('.') and not all: # do not display this file/folder
continue
ff = os.path.join(dir, f)
realfile = os.path.join(realdir, f)
identity = "%s%s" % (key, i)
if os.path.isdir(realfile):
dirList.append({"title": f, "key": identity,
"folder":True, "lazy":True, "path": ff})
elif listfiles:
regex = re.compile("(^.*)\.(.*)", re.VERBOSE)
ext = regex.sub(r'\2', f)
if ext == f:
ext = ""
fileList.append({"title": f, "key": identity,
"extraClasses": "ext_"+ext, "path": ff})
i+=1
return (dirList + fileList)
def makeDirectory(self, dir, filename):
"""Create a directory"""
realdir = self._realdir(dir)
folder = os.path.join(realdir, filename)
if not os.path.exists(folder):
os.mkdir(folder, 0o744)
return "{result: '1'}"
else:
return "{result: '0'}"
def makeFile(self, dir, filename):
"""Create a file in a directory dir taken"""
realdir = self._realdir(dir)
fout = os.path.join(realdir, filename)
if not os.path.exists(fout):
open(fout, 'w')
return "var responce = {result: '1'}"
else:
return "{result: '0'}"
def deleteItem(self, dir, files):
"""Delete a list of files or directories"""
# XXX-Marco do not shadow 'dir'
realdir = self._realdir(dir)
lfiles = unquote(files).split(',,,')
try:
# XXX-Marco do not shadow 'file'
for item in lfiles:
filepath = os.path.join(realdir, item)
if not item or not os.path.exists(filepath):
continue # silent skip file....
details = filepath.split('/')
last = details[-1]
if last and last.startswith('.'):
continue # cannot delete this file/directory, to prevent security
if os.path.isdir(filepath):
shutil.rmtree(filepath)
else:
os.unlink(filepath)
except Exception as e:
return str(e)
return "{result: '1'}"
def copyItem(self, dir, files, del_source=False):
"""Copy a list of files or directory to dir"""
realdir = self._realdir(dir)
lfiles = unquote(files).split(',,,')
try:
# XXX-Marco do not shadow 'file'
for file in lfiles:
realfile = realpath(self.config, file)
if not realfile:
raise NameError('Could not load file or directory %s: Permission denied' % file)
#prepare destination file
details = realfile.split('/')
dest = os.path.join(realdir, details[-1])
if os.path.exists(dest):
raise NameError('NOT ALLOWED OPERATION : File or directory already exists')
if os.path.isdir(realfile):
shutil.copytree(realfile, dest)
if del_source:
shutil.rmtree(realfile)
else:
shutil.copy(realfile, dest)
if del_source:
os.unlink(realfile)
except Exception as e:
return str(e)
return "{result: '1'}"
def rename(self, dir, filename, newfilename):
"""Rename file or directory to dir/filename"""
realdir = self._realdir(dir)
realfile = realpath(self.config, unquote(filename))
if not realfile:
raise NameError('Could not load directory %s: Permission denied' % filename)
tofile = os.path.join(realdir, newfilename)
if not os.path.exists(tofile):
os.rename(realfile, tofile)
return "{result: '1'}"
raise NameError('NOT ALLOWED OPERATION : File or directory already exists')
def copyAsFile(self, dir, filename, newfilename):
"""Copy file or directory to dir/filename"""
realdir = self._realdir(dir)
fromfile = os.path.join(realdir, filename)
tofile = os.path.join(realdir, newfilename)
if not os.path.exists(fromfile):
raise NameError('NOT ALLOWED OPERATION : File or directory does not exist')
while os.path.exists(tofile):
tofile += "_1"
shutil.copy(fromfile, tofile)
return "{result: '1'}"
def uploadFile(self, dir, files):
"""Upload a list of files in directory dir"""
realdir = self._realdir(dir)
for file in files:
if files[file]:
filename = werkzeug.secure_filename(files[file].filename)
if not os.path.exists(os.path.join(dir, filename)):
files[file].save(os.path.join(realdir, filename))
return "{result: '1'}"
def downloadFile(self, dir, filename):
"""Download file dir/filename"""
realdir = self._realdir(dir)
file = os.path.join(realdir, unquote(filename))
if not os.path.exists(file):
raise NameError('NOT ALLOWED OPERATION : File or directory does not exist %s'
% os.path.join(dir, filename))
return file
def zipFile(self, dir, filename, newfilename):
"""Add filename to archive as newfilename"""
realdir = self._realdir(dir)
tozip = os.path.join(realdir, newfilename)
fromzip = os.path.join(realdir, filename)
if not os.path.exists(fromzip):
raise NameError('NOT ALLOWED OPERATION : File or directory does not exist')
if not os.path.exists(tozip):
zip = zipfile.ZipFile(tozip, 'w', zipfile.ZIP_DEFLATED)
if os.path.isdir(fromzip):
rootlen = len(fromzip) + 1
for base, _, files in os.walk(fromzip):
for filename in files:
fn = os.path.join(base, filename).encode("utf-8")
zip.write(fn, fn[rootlen:]) # XXX can fail if 'fromzip' contains multibyte characters
else:
zip.write(fromzip)
zip.close()
return "{result: '1'}"
raise NameError('NOT ALLOWED OPERATION : File or directory already exists')
def unzipFile(self, dir, filename, newfilename):
"""Extract a zipped archive"""
realdir = self._realdir(dir)
target = os.path.join(realdir, newfilename)
archive = os.path.join(realdir, filename)
if not os.path.exists(archive):
raise NameError('NOT ALLOWED OPERATION : File or directory does not exist')
if not os.path.exists(target):
zip = zipfile.ZipFile(archive)
#member = zip.namelist()
zip.extractall(target)
#if len(member) > 1:
# zip.extractall(target)
#else:
# zip.extract(member[0], newfilename)
return "{result: '1'}"
raise NameError('NOT ALLOWED OPERATION : File or directory already exists')
def readFile(self, dir, filename, truncate=False):
"""Read file dir/filename and return content"""
realfile = realpath(self.config, os.path.join(unquote(dir),
unquote(filename)))
if not realfile:
raise NameError('Could not load directory %s: Permission denied' % dir)
if not isText(realfile):
return "FILE ERROR: Cannot display binary file, please open a text file only!"
if not truncate:
return open(realfile).read()
else:
return tail(open(realfile), 0)
# -*- coding: utf-8 -*-
# vim: set et sts=2:
# pylint: disable-msg=W0311,C0301,C0103,C0111
import os
import re
import shutil
from git import Repo
from flask import jsonify
def cloneRepo(url, workDir, user="", email=""):
"""Clone a repository
Args:
workDir is the path of the new project
url is the url of the repository to be cloned
email is the user's email
user is the name of the user"""
if not workDir:
return jsonify(code=0,
result="Can not create project folder.")
if os.path.exists(workDir) and len(os.listdir(workDir)) < 2:
shutil.rmtree(workDir) # delete useless files
repo = Repo.clone_from(url, workDir)
config_writer = repo.config_writer()
config_writer.add_section("user")
if user != "":
config_writer.set_value("user", "name", user.encode("utf-8"))
if email != "":
config_writer.set_value("user", "email", email)
def updateGitConfig(repository, user, email):
if not os.path.exists(repository):
return
repo = Repo(repository)
config_writer = repo.config_writer()
if user != "":
config_writer.set_value("user", "name", user)
if email != "":
config_writer.set_value("user", "email", email)
config_writer.release()
def gitStatus(project):
"""Run git status and return status of specified project folder
Args:
project: path of the projet to get status
Returns:
a list with (result of git status, current branch, isdirty)"""
repo = Repo(project)
git = repo.git
result = git.status().replace('#', '')
branch = git.branch().replace(' ', '').split('\n')
isdirty = repo.is_dirty(untracked_files=True)
return (result, branch, isdirty)
def switchBranch(project, branch):
"""Switch a git branch
Args:
project: directory of the local git repository
name: switch from current branch to `name` branch"""
repo = Repo(project)
current_branch = repo.active_branch.name
if branch == current_branch:
return False
else:
git = repo.git
git.checkout(branch)
return True
def addBranch(project, name, onlyCheckout=False):
"""Add new git branch to the repository
Args:
project: directory of the local git repository
name: name of the new branch
onlyCheckout: if True then the branch `name` is created before checkout
Returns:
True or False"""
if not os.path.exists(project):
return False
repo = Repo(project)
git = repo.git
if not onlyCheckout:
git.checkout('-b', name)
else:
git.checkout(name)
return True
def getDiff(project):
"""Get git diff for the specified project directory"""
result = ""
try:
repo = Repo(project)
git = repo.git
current_branch = repo.active_branch.name
result = git.diff(current_branch)
except Exception as e:
result = safeResult(str(e))
return result
def gitCommit(project, msg):
"""Commit changes for the specified repository
Args:
project: directory of the local repository
msg: commit message"""
code = 0
json = ""
repo = Repo(project)
if repo.is_dirty:
git = repo.git
#add file to be commited
files = repo.untracked_files
for f in files:
git.add(f)
#Commit all modified and untracked files
git.commit('-a', '-m', msg)
code = 1
else:
json = "Nothing to be commited"
return jsonify(code=code, result=json)
def gitPush(project):
"""Push changes for the specified repository
Args:
project: directory of the local repository
msg: commit message"""
code = 0
json = ""
try:
repo = Repo(project)
git = repo.git
#push changes to repo
current_branch = repo.active_branch.name
git.push('origin', current_branch)
code = 1
except Exception as e:
json = safeResult(str(e))
return jsonify(code=code, result=json)
def gitPull(project):
result = ""
code = 0
try:
repo = Repo(project)
git = repo.git
git.pull()
code = 1
except Exception as e:
result = safeResult(str(e))
return jsonify(code=code, result=result)
def safeResult(result):
"""Parse string and remove credential of the user"""
regex = re.compile("(https:\/\/)([\w\d\._-]+:[\w\d\._-]+)\@([\S]+\s)", re.VERBOSE)
return regex.sub(r'\1\3', result)
import atexit
import os
import subprocess
SLAPRUNNER_PROCESS_LIST = []
class Popen(subprocess.Popen):
"""
Extension of Popen to launch and kill processes in a clean way
"""
def __init__(self, *args, **kwargs):
"""
Launch process and add object to process list for handler
"""
self.name = kwargs.pop('name', None)
kwargs['stdin'] = subprocess.PIPE
kwargs['stderr'] = subprocess.STDOUT
kwargs.setdefault('universal_newlines', True)
kwargs.setdefault('stdout', subprocess.PIPE)
kwargs.setdefault('close_fds', True)
subprocess.Popen.__init__(self, *args, **kwargs)
global SLAPRUNNER_PROCESS_LIST
SLAPRUNNER_PROCESS_LIST.append(self)
self.stdin.flush()
self.stdin.close()
self.stdin = None
def isRunning(name):
"""
Return True if a process with this name is running
"""
for process in SLAPRUNNER_PROCESS_LIST:
if process.name == name:
if process.poll() is None:
return True
return False
def killRunningProcess(name, recursive=False):
"""
Kill all processes with a given name
"""
for process in SLAPRUNNER_PROCESS_LIST:
if process.name == name:
process.kill(recursive=recursive)
sigterm_handled = False
def handler(*args, **kwargs):
"""
Signal handler to kill all processes
"""
global sigterm_handled
if sigterm_handled:
return
sigterm_handled = True
for process in SLAPRUNNER_PROCESS_LIST:
try:
process.kill()
except OSError:
pass
def setHandler(sig_list=None):
atexit.register(handler)
def isPidFileProcessRunning(pidfile):
"""
Test if the pidfile exist and if the process is still active
"""
if os.path.exists(pidfile):
try:
pid = int(open(pidfile, 'r').readline())
except ValueError:
pid = None
# XXX This could use psutil library.
if pid and os.path.exists("/proc/%s" % pid):
return True
return False
# -*- coding: utf-8 -*-
# vim: set et sts=2:
# pylint: disable-msg=W0311,C0301,C0103,C0111,R0904,R0903
from six.moves import configparser
import datetime
import flask
import logging
import logging.handlers
import os
from slapos.htpasswd import HtpasswdFile
from slapos.runner.process import setHandler
import sys
from slapos.runner.utils import runInstanceWithLock, startProxy
from slapos.runner.views import *
from slapos.runner.gittools import cloneRepo, switchBranch
from git import GitCommandError
import time
import traceback
TRUE_VALUES = (1, '1', True, 'true', 'True')
class Config:
def __init__(self):
self.configuration_file_path = None
self.console = None
self.log_file = None
self.logger = None
self.verbose = None
def setConfig(self):
"""
Set options given by parameters.
"""
self.configuration_file_path = os.path.abspath(os.getenv('RUNNER_CONFIG'))
# Load configuration file
configuration_parser = configparser.SafeConfigParser()
configuration_parser.read(self.configuration_file_path)
for section in ("slaprunner", "slapos", "slapproxy", "slapformat",
"sshkeys_authority", "gitclient"):
configuration_dict = dict(configuration_parser.items(section))
for key in configuration_dict:
if not getattr(self, key, None):
setattr(self, key, configuration_dict[key])
# set up logging
self.logger = logging.getLogger("slaprunner")
self.logger.setLevel(logging.INFO)
if self.console:
self.logger.addHandler(logging.StreamHandler())
self.log_file = self.log_dir + '/slaprunner.log'
if not os.path.isdir(os.path.dirname(self.log_file)):
# fallback to console only if directory for logs does not exists and
# continue to run
raise ValueError('Please create directory %r to store %r log file' % (
os.path.dirname(self.log_file), self.log_file))
else:
file_handler = logging.FileHandler(self.log_file)
file_handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
self.logger.addHandler(file_handler)
self.logger.info('Configured logging to file %r' % self.log_file)
self.logger.info("Started.")
self.logger.info(os.environ['PATH'])
if self.verbose:
self.logger.setLevel(logging.DEBUG)
self.logger.debug("Verbose mode enabled.")
def checkHtpasswd(config):
"""XXX:set for backward compatiblity
create a htpassword if etc/.users exist"""
user = os.path.join(config['etc_dir'], '.users')
htpasswdfile = os.path.join(config['etc_dir'], '.htpasswd')
if os.path.exists(user) and not os.path.exists(htpasswdfile):
data = open(user).read().strip().split(';')
htpasswd = HtpasswdFile(htpasswdfile, create=True)
htpasswd.update(data[0], data[1])
htpasswd.save()
else:
return
def checkJSONConfig(config):
"""create a default json file with some parameters inside
if the file has never been created"""
json_file = os.path.join(config['etc_dir'], 'config.json')
if not os.path.exists(json_file):
params = {
'run_instance' : True,
'run_software' : True,
'max_run_instance' : 3,
'max_run_software' : 2
}
open(json_file, "w").write(json.dumps(params))
def run():
"Run default configuration."
# Parse arguments
config = Config()
config.setConfig()
if os.getuid() == 0:
# avoid mistakes (mainly in development mode)
raise Exception('Do not run SlapRunner as root.')
serve(config)
def serve(config):
try:
from werkzeug.middleware.proxy_fix import ProxyFix
except ImportError:
from werkzeug.contrib.fixers import ProxyFix
workdir = os.path.join(config.runner_workdir, 'project')
software_link = os.path.join(config.runner_workdir, 'softwareLink')
app.config.update(**config.__dict__)
app.config.update(
software_log=config.software_root.rstrip('/') + '.log',
instance_log=config.instance_root.rstrip('/') + '.log',
format_log=config.instance_root.rstrip('/') + '.log',
workspace=workdir,
software_link=software_link,
instance_profile='instance.cfg',
software_profile='software.cfg',
SECRET_KEY=os.urandom(24),
PERMANENT_SESSION_LIFETIME=datetime.timedelta(days=31),
)
checkHtpasswd(app.config)
checkJSONConfig(app.config)
if not os.path.exists(workdir):
os.mkdir(workdir)
if not os.path.exists(software_link):
os.mkdir(software_link)
setHandler()
app.logger.addHandler(config.logger)
repo_url = app.config['default_repository']
branch_name = app.config.get('default_repository_branch', '')
repo_name = os.path.basename(repo_url.replace('.git', ''))
try:
repository_path = os.path.join(workdir, repo_name)
app.config.update(default_repository_path=repository_path)
if len(os.listdir(workdir)) == 0 or not os.path.exists(repository_path):
app.logger.info('cloning repository %s...' % repo_url)
result = cloneRepo(repo_url, repository_path)
if branch_name:
switchBranch(repository_path, branch_name)
except GitCommandError as e:
app.logger.warning('Error while cloning default repository: %s' % str(e))
traceback.print_exc()
# Start slapproxy here when runner is starting
app.logger.info('Stating slapproxy...')
startProxy(app.config)
app.logger.info('Running slapgrid...')
if app.config['auto_deploy_instance'] in TRUE_VALUES:
from six.moves import _thread
# XXX-Nicolas: Hack to be sure that supervisord has started
# before any communication with it, so that gunicorn doesn't exit
_thread.start_new_thread(waitForRun, (app.config,))
config.logger.info('Done.')
app.wsgi_app = ProxyFix(app.wsgi_app)
def waitForRun(config):
time.sleep(3)
runInstanceWithLock(config)
def getUpdatedParameter(self, var):
configuration_parser = configparser.SafeConfigParser()
configuration_file_path = os.path.abspath(os.getenv('RUNNER_CONFIG'))
configuration_parser.read(configuration_file_path)
for section in configuration_parser.sections():
if configuration_parser.has_option(section, var):
return configuration_parser.get(section, var)
# if the requested var is dependant of flask
if var in self.keys():
temp_dict = dict()
temp_dict.update(self)
return temp_dict[var]
else:
raise KeyError
if __name__ == 'slapos.runner.run':
flask.config.Config.__getitem__ = getUpdatedParameter
run()
This diff is collapsed.
/*
ColorBox Core Style:
The following CSS is consistent between example themes and should not be altered.
*/
#colorbox, #cboxOverlay, #cboxWrapper{position:absolute; top:0; left:0; z-index:9999; overflow:hidden;}
#cboxOverlay{position:fixed; width:100%; height:100%;}
#cboxMiddleLeft, #cboxBottomLeft{clear:left;}
#cboxContent{position:relative;}
#cboxLoadedContent{overflow:auto;}
#cboxTitle{margin:0;}
#cboxLoadingOverlay, #cboxLoadingGraphic{position:absolute; top:0; left:0; width:100%; height:100%;}
#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{cursor:pointer;}
.cboxPhoto{float:left; margin:auto; border:0; display:block;}
.cboxIframe{width:100%; height:100%; display:block; border:0;}
/*
User Style:
Change the following styles to modify the appearance of ColorBox. They are
ordered & tabbed in a way that represents the nesting of the generated HTML.
*/
#cboxOverlay{background:#000;}
#colorbox{}
#cboxTopLeft{/*width:7px; height:7px; background:#000; opacity:0.6;*/}
#cboxTopCenter{/*height:7px; background:#000; opacity:0.6;*/}
#cboxTopRight{/*width:7px; height:7px; background:#000; opacity:0.6;*/}
#cboxBottomLeft{/*width:7px; height:43px; background:#000; opacity:0.6;*/}
#cboxBottomCenter{height:36px; background: #E7E6E6; /*border-bottom: 7px solid rgba(0,0,0,0.8);*/}
#cboxBottomRight{/*width:7px; height:43px; background:#000; opacity:0.6;*/}
#cboxMiddleLeft{/*width:7px; background:#000; opacity:0.6;*/}
#cboxMiddleRight{/*width:7px; background:#000; opacity:0.6;*/}
#cboxContent{background:#fff; overflow:visible;}
.cboxIframe{background:#fff;}
#cboxError{padding:50px; border:1px solid #ccc;}
#cboxLoadedContent{margin-bottom:5px;}
#cboxLoadingOverlay{background:url(images/loading_background.png) no-repeat center center;}
#cboxLoadingGraphic{background:url(images/loading.gif) no-repeat center center;}
#cboxTitle{position:absolute; bottom:-27px; left:0; text-align:left; width:100%; font-weight:bold; color:#7C7C7C; font-size:16px; margin-left: 12px;}
#cboxCurrent{position:absolute; bottom:-25px; left:58px; font-weight:bold; color:#7C7C7C;}
#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{position:absolute; border: none; bottom:-29px; margin-right:8px; background:url(images/controls.png) no-repeat 0px 0px; width:23px; height:23px; text-indent:-9999px;}
#cboxPrevious{left:0px; background-position: -51px -25px;}
#cboxPrevious:hover{background-position:-51px 0px;}
#cboxNext{left:27px; background-position:-75px -25px;}
#cboxNext:hover{background-position:-75px 0px;}
#cboxClose{right:0; background-position:-100px -25px;}
#cboxClose:hover{background-position:-100px 0px;}
.cboxSlideshow_on #cboxSlideshow{background-position:-125px 0px; right:27px;}
.cboxSlideshow_on #cboxSlideshow:hover{background-position:-150px 0px;}
.cboxSlideshow_off #cboxSlideshow{background-position:-150px -25px; right:27px;}
.cboxSlideshow_off #cboxSlideshow:hover{background-position:-125px 0px;}
\ No newline at end of file
.editor {
margin: 0;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.box_header{
background: #E4E4E4;
width: 100%;
height: 30px;
color: #737373;
border-bottom: 4px solid #7FAED3;
min-width: 936px;
}
.box_header ul{float: left; padding-top: 2px; text-shadow: 0px 1px #F1F1F1;}
.box_header li{float: left;border: 1px solid #AAB8C2;padding: 1px 5px 1px 0;margin-left: 5px; text-indent: 5px;}
/*.box_header li:last-child{border:none}*/
.box_header li > span{cursor: pointer; height: 20px; display: block;line-height: 20px;font-weight: bold;padding: 1px;}
.box_header li:hover{border: 1px solid #57A1D6;background-color: #C7C7C7;}
.box_header li:last-child{margin-right: 5px;}
.box_header li>a{font-weight: bold; font-size:1em;display: block;padding: 2px;}
.save_btn{background: url(../images/icon_save.png) center right no-repeat;width: 25px;}
.expand_editor{background: url(../images/fullscreen.png) center right no-repeat;width: 23px;}
.e_expanded{background: url(../images/fullscreen_exit.png) center right no-repeat;}
.swith_btn{background: url(../images/gnome-session-switch.png) center right no-repeat;width: 105px;}
.flist_btn{background: url(../images/list2_down.png) center right no-repeat;width: 26px;}
.fmenu_btn{background: url(../images/ui_menu_blue.png) center right no-repeat;width: 58px;}
.shell_btn{background: url(../images/terminal.png) center right no-repeat;width: 62px;}
#tabControl{overflow: hidden;}
#tabControl .item{float:left; min-width: 60px; background: #D5D5D5; height: 22px; padding-top: 8px; font-size:1em;
border-left:1px #E4E4E4 solid; cursor:pointer; color: #5C7077; text-shadow: 0px 1px #E6E6E6; position: relative;}
#tabControl .item:hover{background: #C7C7C7;}
#tabControl.item:last-child{margin-right:none; overflow: hidden}
#tabControl .active{background: #7FAED3; color: #fff; text-shadow: none;}
#tabControl .active:hover{background: #7FAED3;}
#tabControl .item span{padding: 0 4px; display: block; float: left; text-overflow:clip; max-width: 126px; white-space:nowrap; overflow: hidden;}
#tabControl .active span.bt_close{color: #DBDBDB;}
#tabControl .active span.bt_close:hover{color: #fff;}
#tabContent pre {display: none;}
#tabContent pre.active {display: block;}
.item-hide{display:none;}
#shell-window {
width: 974px;
height: 350px;
z-index: 10;
border: 2px solid #6A93A0;
border-top: none;
background: #9bbed6;
position: absolute;
display: none;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
}
#shell-result {
width: 98%;
height: 300px;
margin-left: 1%;
margin-right: 1%;
margin-top: 5px;
padding: 3px;
background: #E4E4E4;
color: #074A86;
box-sizing: border-box;
border: 2px inset #6A93A0;
overflow: auto;
}
.runned-command
{
font-weight: bold;
}
#shell-input {
width: 96%;
margin-left: 1%;
margin-right: 1%;
position: absolute;
bottom: 10px;
background: #E4E4E4;
color: #074A86;
border: inset 1px #678dad;
}
UL.jqueryFileTree {
font-family: Verdana, sans-serif;
font-size: 12px;
line-height: 18px;
padding: 0px;
margin: 0px;
}
UL.jqueryFileTree LI {
list-style: none;
padding: 0px;
padding-left: 20px;
margin: 0px;
white-space: nowrap;
}
UL.jqueryFileTree A {
color: #333;
text-decoration: none;
}
UL.jqueryFileTree A:hover {
background: #BDF;
}
.gs_dir_list{display: none;}
.gs_dir_content {
height: 100%;
width: 100%;
}
.gs_dir_content_menu {
padding: 0 0 0 5px;
margin-bottom: 5px;
border: 1px solid #BBBBBB;
background-color: white;
}
.gs_dir_content_button {
background: url("data:image/gif;base64,R0lGODlhKAAVAJEAAPn5+e7u7uPj4////yH5BAEAAAMALAAAAAAoABUAAAIxhI+py+0Po5y02ouz3rz7FoTiSJbmiabqGgruC8fyTNf2jef6zvf+DwwKh8Si8SgsAAA7") repeat-x scroll 0 0 transparent;
border: 1px outset #BBBBBB;
cursor: pointer;
margin-right: 7px;
font-weight: bold;
padding: 1px;
background-position: 0, 50;
text-decoration: none;
color: black;
}
.gs_dir_content_button:hover {
color: #EF7929;
border: 1px inset #BBBBBB;
}
.gs_dir_content_files {
min-height: 250px;
max-height: 400px;
overflow: auto;
background-color: white;
border: 1px solid #BBBBBB;
}
div.browser div.gs_dir_content_files{min-height:330px;}
#curDir {
height: 30px;
line-height: 30px;
}
#gsClipBoard{display: none;}
.directory_info {
padding-left: 20px;
}
.directory_info a {
text-decoration: none;
}
#rootLink {
display: none;
}
.loadingDiv {
width: 100%;
height: 75px;
margin-top: 50px;
background: url(images/loading.gif) center center no-repeat;
}
.demoto {
width: 100%;
height: 100%;
}
.dir_index {
cursor: pointer;
}
.toggleplus {
background: url('images/toggle_plus.png') left top no-repeat;
}
.toggleminus {
background: url('images/toggle_minus.png') left top no-repeat;
}
.dirs_files_table {
width: 100%;
font-size: 12px;
}
.dirs_files_table td{padding: 0;border:none}
.gsItem {padding: 4px; padding-left: 27px;}
.file_ext_name {
font-weight: normal;
color: #FF6600;
}
.gsHeadTable {
width :100%;
}
.dirs_files_table tr:hover {background: #BDF;}
.gsHeadText {
color:#666;
height:34px;
font-weight: bold;
}
.gs_head{padding: 10px 5px 10px 0;}
.gs_title{padding-left: 10px; margin:5px 5px 0 0; background: #e4e4e4; height:30px;}
.rowSelected {
color: #FFF;
background-color: #c2c2c2;
}
.rowSelected a {
color: #FFF;
}
.pathlink {cursor: pointer; font-size: 13px; margin-right: 2px; margin-left: 2px;}
.gs_delimiter {
height: 16px;
line-height: 16px;
}
/* Core Styles */
.directory {
background-image:url(images/directory.png);
background-repeat:no-repeat;
}
.directoryMeny a { background: url(images/directory.png) left top no-repeat; padding-left: 20px}
/*.jqueryFileTree LI.expanded { background: url(images/folder_open.png) 20px top no-repeat; }*/
.file {
background-image:url(images/file.png);
background-repeat:no-repeat;
cursor:pointer;
margin-left: 5px;
margin-top: 2px;
padding-left: 22px;
}
.jqueryFileTree LI.wait { background: url(images/spinner.gif) left top no-repeat; }
/* File Extensions*/
.ext_3gp {
background-image: url(images/film.png) left top no-repeat;
}
.ext_afp {
background-image: url(images/code.png); }
.ext_afpa {
background-image: url(images/code.png); }
.ext_asp {
background-image: url(images/code.png); }
.ext_aspx {
background-image: url(images/code.png); }
.ext_avi {
background-image: url(images/film.png); }
.ext_bat {
background-image: url(images/application.png); }
.ext_bmp {
background-image: url(images/picture.png); }
.ext_c {
background-image: url(images/code.png); }
.ext_cfm {
background-image: url(images/code.png); }
.ext_cgi {
background-image: url(images/code.png); }
.ext_com {
background-image: url(images/application.png); }
.ext_cpp {
background-image: url(images/code.png); }
.ext_css {
background-image: url(images/css.png); }
.ext_doc {
background-image: url(images/doc.png); }
.ext_exe {
background-image: url(images/application.png); }
.ext_gif {
background-image: url(images/picture.png);
}
.ext_fla {
background-image: url(images/flash.png); }
.ext_h {
background-image: url(images/code.png); }
.ext_htm {
background-image: url(images/html.png); }
.ext_html {
background-image: url(images/html.png); }
.ext_jar {
background-image: url(images/java.png); }
.ext_jpg {
background-image: url(images/picture.png);
}
.ext_jpeg {
background-image: url(images/picture.png); }
.ext_js {
background-image: url(images/script.png) ; }
.ext_lasso {
background-image: url(images/code.png) ; }
.ext_log {
background-image: url(images/txt.png) ; }
.ext_m4p {
background-image: url(images/music.png); }
.ext_mov {
background-image: url(images/film.png); }
.ext_mp3 {
background-image: url(images/music.png); }
.ext_mp4 {
background-image: url(images/film.png); }
.ext_mpg {
background-image: url(images/film.png); }
.ext_mpeg {
background-image: url(images/film.png); }
.ext_ogg {
background-image: url(images/music.png); }
.ext_pcx {
background-image: url(images/picture.png); }
.ext_pdf {
background-image: url(images/pdf.png); }
.ext_php {
background-image: url(images/php.png); }
.ext_png {
background-image: url(images/picture.png); }
.ext_ppt {
background-image: url(images/ppt.png); }
.ext_psd {
background-image: url(images/psd.png); }
.ext_pl {
background-image: url(images/script.png);; }
.ext_py {
background-image: url(images/script.png) ;}
.ext_rb {
background-image: url(images/ruby.png); }
.ext_rbx {
background-image: url(images/ruby.png); }
.ext_rhtml {
background-image: url(images/ruby.png); }
.ext_rpm {
background-image: url(images/linux.png); }
.ext_ruby {
background-image: url(images/ruby.png); }
.ext_sql {
background-image: url(images/db.png); }
.ext_swf {
background-image: url(images/flash.png); }
.ext_tif {
background-image: url(images/picture.png); }
.ext_tiff {
background-image: url(images/picture.png); }
.ext_txt {
background-image: url(images/txt.png) ; }
.ext_vb {
background-image: url(images/code.png) ; }
.ext_wav {
background-image: url(images/music.png) ; }
.ext_wmv {
background-image: url(images/film.png); }
.ext_xls {
background-image: url(images/xls.png) ; }
.ext_xml {
background-image: url(images/code.png) ; }
.ext_zip {
background-image: url(images/zip.png) ; }
.ext_cfg {
background-image: url(images/cfg.png) ; }
/* Generic context menu styles */
.contextMenu {
position: absolute;
width: 160px;
z-index: 99999;
border: solid 1px #CCC;
background: #ffffff;
padding: 0px;
margin: 0px;
display: none;
box-shadow: 2px 2px 6px rgba(0,0,0,.2);
}
.contextMenu LI {
list-style: none;
padding: 0px;
margin: 0px;
border: none;
}
.contextMenu A {
color: #333;
text-decoration: none;
display: block;
line-height: 20px;
height: 20px;
background-position: 6px center;
background-repeat: no-repeat;
outline: none;
padding: 1px 5px;
padding-left: 28px;
}
.contextMenu LI.hover A {
color: #FFF;
background-color: #3399FF;
}
.contextMenu LI.disabled A {
color: #AAA;
cursor: default;
}
.contextMenu LI.hover.disabled A {
background-color: transparent;
}
.contextMenu LI.separator {
border-top: solid 1px #CCC;
}
/*
Adding Icons
You can add icons to the context menu by adding
classes to the respective LI element(s)
*/
.contextMenu LI.edit A { background-image: url(images/page_white_edit.png); }
.contextMenu LI.cut A { background-image: url(images/cut.png); }
.contextMenu LI.copy A { background-image: url(images/page_white_copy.png); }
.contextMenu LI.paste A { background-image: url(images/page_white_paste.png); }
.contextMenu LI.delete A { background-image: url(images/cross.png); }
.contextMenu LI.quit A { background-image: url(images/door.png); }
.contextMenu LI.directorymenu A { background-image: url(images/folder_open.png); }
.contextMenu LI.rename A { background-image: url(images/mfile.png); }
.contextMenu LI.download A { background-image: url(images/disk.png); }
.contextMenu LI.notepad A { background-image: url(images/page_white_edit.png); }
.contextMenu LI.picture A { background-image: url(images/mpicture.png); }
.contextMenu LI.newfile A { background-image: url(images/new_file.png); }
.contextMenu LI.newdir A { background-image: url(images/new_dir.png); }
.contextMenu LI.uploadfolder A { background-image: url(images/upload_folder.png); }
.contextMenu LI.selection A { background-image: url(images/selection-select.png); }
.contextMenu LI.zip A { background-image: url(images/mzip.png); }
\ No newline at end of file
[Dolphin]
ShowPreview=true
Timestamp=2012,1,11,18,9,34
Version=2
/*! jQuery UI - v1.10.3 - 2013-10-14
* http://jqueryui.com
* Includes: jquery.ui.core.css, jquery.ui.resizable.css, jquery.ui.selectable.css
* Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */
.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{min-height:0}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}
\ No newline at end of file
/* Generic context menu styles */
.contextMenu {
position: absolute;
width: 200px;
z-index: 99999;
border: solid 1px #CCC;
background: #ffffff;
padding: 0px;
margin: 0px;
display: none;
box-shadow: 2px 2px 6px rgba(0,0,0,.2);
}
.contextMenu LI {
list-style: none;
padding: 0px;
margin: 0px;
border: none;
}
.contextMenu A {
color: #333;
text-decoration: none;
display: block;
line-height: 20px;
height: 20px;
background-position: 6px center;
background-repeat: no-repeat;
outline: none;
padding: 1px 5px;
padding-left: 28px;
}
.contextMenu LI.hover A {
color: #FFF;
background-color: #3875D7;
}
.contextMenu LI.disabled A {
color: #AAA;
cursor: default;
}
.contextMenu LI.hover.disabled A {
background-color: transparent;
}
.contextMenu LI.separator {
border-top: solid 1px #CCC;
}
/*
Adding Icons
You can add icons to the context menu by adding
classes to the respective LI element(s)
*/
.contextMenu LI.edit A { background-image: url(images/page_white_edit.png); }
.contextMenu LI.cut A { background-image: url(images/cut.png); }
.contextMenu LI.copy A { background-image: url(images/page_white_copy.png); }
.contextMenu LI.paste A { background-image: url(images/page_white_paste.png); }
.contextMenu LI.delete A { background-image: url(images/cross.png); }
.contextMenu LI.quit A { background-image: url(images/door.png); }
.contextMenu LI.newfile A { background-image: url(images/new_file.png); }
.contextMenu LI.newdir A { background-image: url(images/new_dir.png); }
.contextMenu LI.view A { background-image: url(images/mpicture.png); }
.contextMenu LI.rename A { background-image: url(images/mfile.png); }
.contextMenu LI.md5sum A { background-image: url(images/selection-select.png); }
.contextMenu LI.refresh A { background-image: url(images/refresh.png); }
.contextMenu LI.favorite A { background-image: url(images/knewstuff.png); }
.contextMenu LI.uploadfile A { background-image: url(images/upload_folder.png); }
\ No newline at end of file
#tabContainer{
background: #D5D5D5;
padding:3px;
/*position:relative;*/
width:972px;
font-size: 14px;
border-radius: 4px 4px 0 0;
}
#tabContainer textarea {
width:702px;
}
#tabContainer textarea.slap{white-space: pre-wrap;word-wrap: break-word;overflow: hidden;color: #6F6F6F;width:616px; max-height:120px;
resize: none; height:18px;padding:3px;min-height:18px;font-size: 13px;}
#tabContainer textarea.mb_style{width:713px;}
#tabContainer > ul{
overflow:hidden;
height:34px;
position:absolute;
z-index:80;
margin: 0;
}
#tabContainer > ul > li{
float:left;
list-style:none;
font-family: Arial;
}
#tabContainer > ul > li a{
background:#D5D5D5;
border-left:1px solid #fcfcfc;
cursor:pointer;
display:block;
height:34px;
line-height:34px;
padding:0 30px;
text-decoration:none;
color: #526063;
text-shadow: 0px 1px #ECECEC;
font-size: 17px;
}
#tabContainer > ul > li a:hover{
background:#E2E2E2;
}
#tabContainer > ul > li a.active{
background:#fff;
border:1px solid #fff;
border-top:0;
border-right:0;
color:#333;
}
#tabContainer > ul > li:first-child a{border-left:0}
.tabDetails{
background:#fff;
border-top: none;
margin:34px 0 0;
}
.tabContents{
padding:20px;
min-height: 250px;
}
.tabContents p{
padding:0 0 10px;
}
This diff is collapsed.
/*** ESSENTIAL STYLES ***/
.sf-menu, .sf-menu * {
margin: 0;
padding: 0;
list-style: none;
}
.sf-menu li {
position: relative;
}
.sf-menu ul {
position: absolute;
display: none;
top: 100%;
right: 0;
z-index: 99;
}
.sf-menu > li {
float: left;
}
.sf-menu li:hover > ul,
.sf-menu li.sfHover > ul {
display: block;
}
.sf-menu > li.right_menu{
float:right;
}
.sf-menu a {
display: block;
position: relative;
}
.sf-menu ul ul {
top: 0;
left:0;
}
/*** DEMO SKIN ***/
.sf-menu {
float: left;
margin-bottom: 1em;
}
.sf-menu ul {
box-shadow: 2px 2px 6px rgba(0,0,0,.2);
min-width: 16em; /* allow long menu items to determine submenu width */
*width: 16em; /* no auto sub width for IE7, see white-space comment below */
border: 1px solid #C2C2C2;
background: #E4E4E4;
border-left:none;
padding-bottom:2px;
}
.sf-menu li a {
display:block;
height: 20px;
color: #074A86;
border-right: 1px solid #c2c2c2;
font-weight:bold;
font-size:15px;
text-decoration:none;
padding:7px 20px;
zoom: 1; /* IE7 */
}
.sf-menu li {
white-space: nowrap; /* no need for Supersubs plugin */
*white-space: normal; /* ...unless you support IE7 (let it wrap) */
}
.sf-menu li.right_menu > a{
border-left: 1px solid #c2c2c2;
border-right:none;
width: 35px;
padding:7px;
}
.sf-menu li.main_menu{
background: url(../images/main_menu.png) center no-repeat;
}
.sf-menu li.slapos_run{
background: url(../images/run_button.png) center no-repeat;
}
.sf-menu li.slapos_stop{
background: url(../images/stop_button.png) center no-repeat;
}
.sf-menu li:hover,
.sf-menu li.sfHover {
color: #fff;/*#0271BF;*/
background:#c2c2c2;
/* only transition out, not in */
-webkit-transition: none;
transition: none;
}
.sf-menu li.main_menu:hover,
.sf-menu li.main_menu.sfHover {
background: #c2c2c2 url(../images/main_menu_hover.png) center no-repeat;
}
.sf-menu li.slapos_stop:hover,
.sf-menu li.slapos_stop.sfHover{
background: #c2c2c2 url(../images/stop_button2.png) center no-repeat;
}
.sf-menu li.slapos_run:hover,
.sf-menu li.slapos_run.sfHover{
background: #c2c2c2 url(../images/run_button2.png) center no-repeat;
}
.sf-menu li a:hover{
color: #fff;
}
.sf-menu ul li {
background: #E4E4E4;
cursor: pointer;
color: #000;
}
.sf-menu ul li.sep{border-bottom: 1px solid #c2c2c2; margin: 3px 0;}
.sf-menu ul li.sep:hover{
background: #E4E4E4;
}
.sf-menu ul ul li {
background: #E4E4E4;
}
.sf-menu ul ul{
border: 1px solid #C2C2C2;
border-left:none;
}
.sf-menu ul li a{
font-weight:normal;
font-size:14px;
color: #000;
height: 16px;
padding:7px;
padding-left: 15px;
border:none;
}
.sf-menu ul li:hover,
.sf-menu ul li.sfHoverhover{
background: #A3A3A3;
}
.sf-menu ul li:hover a,
.sf-menu ul li.sfHoverhover a{
color: #fff;
}
/*** arrows (for all except IE7) **/
.sf-arrows .sf-with-ul {
padding-right: 2.5em;
*padding-right: 1em; /* no CSS arrows for IE7 (lack pseudo-elements) */
}
/* styling for both css and generated arrows */
.sf-arrows .sf-with-ul:after {
content: '';
position: absolute;
top: 50%;
right: 1em;
margin-top: -3px;
height: 0;
width: 0;
/* order of following 3 rules important for fallbacks to work */
border: 5px solid transparent;
border-top-color: #dFeEFF; /* edit this to suit design (no rgba in IE8) */
border-top-color: rgba(255,255,255,.5);
}
.sf-arrows > li > .sf-with-ul:focus:after,
.sf-arrows > li:hover > .sf-with-ul:after,
.sf-arrows > .sfHover > .sf-with-ul:after {
border-top-color: white; /* IE8 fallback colour */
}
/* styling for right-facing arrows */
.sf-arrows ul .sf-with-ul:after {
margin-top: -5px;
margin-right: -3px;
border-color: transparent;
border-left-color: #dFeEFF; /* edit this to suit design (no rgba in IE8) */
border-left-color: rgba(255,255,255,.5);
}
.sf-arrows ul li > .sf-with-ul:focus:after,
.sf-arrows ul li:hover > .sf-with-ul:after,
.sf-arrows ul .sfHover > .sf-with-ul:after {
border-left-color: white;
}
This diff is collapsed.
[Dolphin]
ShowPreview=true
Timestamp=2012,1,6,11,14,34
Version=2
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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 to comment