Compare commits
No commits in common. "main" and "main" have entirely different histories.
DockerfileUbuntupyproject.toml
src/snek
app.py
docs
form/settings
model
service
static
system
templates
view
webdav.pyterminal
@ -6,6 +6,6 @@ RUN wget https://retoor.molodetz.nl/api/packages/retoor/generic/r/1.0.0/r
|
||||
|
||||
RUN chmod +x r
|
||||
|
||||
RUN mv r /usr/local/bin
|
||||
RUN cp r /usr/local/bin
|
||||
|
||||
CMD ["r"]
|
||||
|
@ -16,7 +16,7 @@ requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"mkdocs>=1.4.0",
|
||||
"lxml",
|
||||
"IPython",
|
||||
|
||||
"shed",
|
||||
"app @ git+https://retoor.molodetz.nl/retoor/app",
|
||||
"beautifulsoup4",
|
||||
|
@ -17,8 +17,8 @@ from aiohttp_session import (
|
||||
setup as session_setup,
|
||||
)
|
||||
from aiohttp_session.cookie_storage import EncryptedCookieStorage
|
||||
|
||||
from app.app import Application as BaseApplication
|
||||
|
||||
from snek.docs.app import Application as DocsApplication
|
||||
from snek.mapper import get_mappers
|
||||
from snek.service import get_services
|
||||
@ -44,8 +44,6 @@ from snek.view.status import StatusView
|
||||
from snek.view.terminal import TerminalSocketView, TerminalView
|
||||
from snek.view.upload import UploadView
|
||||
from snek.view.web import WebView
|
||||
from snek.view.stats import StatsView
|
||||
from snek.view.user import UserView
|
||||
from snek.webdav import WebdavApplication
|
||||
|
||||
SESSION_KEY = b"c79a0c5fda4b424189c427d28c9f7c34"
|
||||
@ -87,18 +85,12 @@ class Application(BaseApplication):
|
||||
self.jinja2_env.add_extension(EmojiExtension)
|
||||
|
||||
self.setup_router()
|
||||
self.executor = None
|
||||
|
||||
self.cache = Cache(self)
|
||||
self.services = get_services(app=self)
|
||||
self.mappers = get_mappers(app=self)
|
||||
self.on_startup.append(self.prepare_asyncio)
|
||||
self.on_startup.append(self.prepare_database)
|
||||
|
||||
async def prepare_asyncio(self, app):
|
||||
# app.loop = asyncio.get_running_loop()
|
||||
app.executor = ThreadPoolExecutor(max_workers=200)
|
||||
app.loop.set_default_executor(self.executor)
|
||||
|
||||
async def create_task(self, task):
|
||||
await self.tasks.put(task)
|
||||
|
||||
@ -171,8 +163,6 @@ class Application(BaseApplication):
|
||||
self.router.add_view("/terminal.html", TerminalView)
|
||||
self.router.add_view("/drive.json", DriveView)
|
||||
self.router.add_view("/drive/{drive}.json", DriveView)
|
||||
self.router.add_view("/stats.json", StatsView)
|
||||
self.router.add_view("/user/{user}.html", UserView)
|
||||
self.webdav = WebdavApplication(self)
|
||||
self.add_subapp("/webdav", self.webdav)
|
||||
|
||||
@ -245,6 +235,11 @@ class Application(BaseApplication):
|
||||
return await super().render_template(template, request, context)
|
||||
|
||||
|
||||
executor = ThreadPoolExecutor(max_workers=200)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.set_default_executor(executor)
|
||||
|
||||
app = Application(db_path="sqlite:///snek.db")
|
||||
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import pathlib
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
from app.app import Application as BaseApplication
|
||||
|
||||
from snek.system.markdown import MarkdownExtension
|
||||
|
||||
|
||||
|
@ -1,25 +1,14 @@
|
||||
from snek.system.form import Form, FormButtonElement, FormInputElement, HTMLElement
|
||||
from snek.system.form import Form, FormInputElement, FormButtonElement, HTMLElement
|
||||
|
||||
|
||||
class SettingsProfileForm(Form):
|
||||
|
||||
nick = FormInputElement(
|
||||
name="nick",
|
||||
required=True,
|
||||
place_holder="Your Nickname",
|
||||
min_length=1,
|
||||
max_length=20,
|
||||
)
|
||||
nick = FormInputElement(name="nick", required=True, place_holder="Your Nickname", min_length=1, max_length=20)
|
||||
action = FormButtonElement(
|
||||
name="action", value="submit", text="Save", type="button"
|
||||
)
|
||||
title = HTMLElement(tag="h1", text="Profile")
|
||||
profile = FormInputElement(
|
||||
name="profile",
|
||||
place_holder="Tell about yourself.",
|
||||
required=False,
|
||||
max_length=300,
|
||||
)
|
||||
profile = FormInputElement(name="profile", place_holder="Tell about yourself.", required=False,max_length=300)
|
||||
action = FormButtonElement(
|
||||
name="action", value="submit", text="Save", type="button"
|
||||
)
|
||||
)
|
@ -29,28 +29,6 @@ class UserModel(BaseModel):
|
||||
|
||||
last_ping = ModelField(name="last_ping", required=False, kind=str)
|
||||
|
||||
async def get_property(self, name):
|
||||
prop = await self.app.services.user_property.find_one(
|
||||
user_uid=self["uid"], name=name
|
||||
)
|
||||
if prop:
|
||||
return prop["value"]
|
||||
|
||||
async def has_property(self, name):
|
||||
return await self.app.services.user_property.exists(
|
||||
user_uid=self["uid"], name=name
|
||||
)
|
||||
|
||||
async def set_property(self, name, value):
|
||||
if not await self.has_property(name):
|
||||
await self.app.services.user_property.insert(
|
||||
user_uid=self["uid"], name=name, value=value
|
||||
)
|
||||
else:
|
||||
await self.app.services.user_property.update(
|
||||
user_uid=self["uid"], name=name, value=value
|
||||
)
|
||||
|
||||
async def get_channel_members(self):
|
||||
async for channel_member in self.app.services.channel_member.find(
|
||||
user_uid=self["uid"], is_banned=False, deleted_at=None
|
||||
|
@ -1,3 +1,5 @@
|
||||
import mimetypes
|
||||
|
||||
from snek.system.model import BaseModel, ModelField
|
||||
|
||||
|
||||
@ -5,3 +7,4 @@ class UserPropertyModel(BaseModel):
|
||||
user_uid = ModelField(name="user_uid", required=True, kind=str)
|
||||
name = ModelField(name="name", required=True, kind=str)
|
||||
value = ModelField(name="path", required=True, kind=str)
|
||||
|
||||
|
@ -9,7 +9,6 @@ from snek.service.drive_item import DriveItemService
|
||||
from snek.service.notification import NotificationService
|
||||
from snek.service.socket import SocketService
|
||||
from snek.service.user import UserService
|
||||
from snek.service.user_property import UserPropertyService
|
||||
from snek.service.util import UtilService
|
||||
from snek.system.object import Object
|
||||
|
||||
@ -28,7 +27,6 @@ def get_services(app):
|
||||
"util": UtilService(app=app),
|
||||
"drive": DriveService(app=app),
|
||||
"drive_item": DriveItemService(app=app),
|
||||
"user_property": UserPropertyService(app=app),
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -10,10 +10,6 @@ class ChannelMemberService(BaseService):
|
||||
channel_member["new_count"] = 0
|
||||
return await self.save(channel_member)
|
||||
|
||||
async def get_user_uids(self, channel_uid):
|
||||
async for model in self.mapper.query("SELECT user_uid FROM channel_member WHERE channel_uid=:channel_uid", {"channel_uid": channel_uid}):
|
||||
yield model["user_uid"]
|
||||
|
||||
async def create(
|
||||
self,
|
||||
channel_uid,
|
||||
|
@ -16,8 +16,9 @@ class SocketService(BaseService):
|
||||
try:
|
||||
await self.ws.send_json(data)
|
||||
except Exception as ex:
|
||||
print(ex, flush=True)
|
||||
self.is_connected = False
|
||||
return self.is_connected
|
||||
return True
|
||||
|
||||
async def close(self):
|
||||
if not self.is_connected:
|
||||
@ -42,6 +43,7 @@ class SocketService(BaseService):
|
||||
self.users[user_uid].add(s)
|
||||
|
||||
async def subscribe(self, ws, channel_uid, user_uid):
|
||||
return
|
||||
if channel_uid not in self.subscriptions:
|
||||
self.subscriptions[channel_uid] = set()
|
||||
s = self.Socket(ws, await self.app.services.user.get(uid=user_uid))
|
||||
@ -55,12 +57,10 @@ class SocketService(BaseService):
|
||||
return count
|
||||
|
||||
async def broadcast(self, channel_uid, message):
|
||||
try:
|
||||
async for user_uid in self.services.channel_member.get_user_uids(channel_uid):
|
||||
print(user_uid, flush=True)
|
||||
await self.send_to_user(user_uid, message)
|
||||
except Exception as ex:
|
||||
print(ex, flush=True)
|
||||
async for channel_member in self.app.services.channel_member.find(
|
||||
channel_uid=channel_uid
|
||||
):
|
||||
await self.send_to_user(channel_member["user_uid"], message)
|
||||
return True
|
||||
|
||||
async def delete(self, ws):
|
||||
|
@ -10,7 +10,7 @@ class UserService(BaseService):
|
||||
async def search(self, query, **kwargs):
|
||||
query = query.strip().lower()
|
||||
if not query:
|
||||
return []
|
||||
raise []
|
||||
results = []
|
||||
async for result in self.find(username={"ilike": "%" + query + "%"}, **kwargs):
|
||||
results.append(result)
|
||||
|
@ -1,33 +0,0 @@
|
||||
import json
|
||||
|
||||
from snek.system.service import BaseService
|
||||
|
||||
|
||||
class UserPropertyService(BaseService):
|
||||
mapper_name = "user_property"
|
||||
|
||||
async def set(self, user_uid, name, value):
|
||||
self.mapper.db["user_property"].upsert(
|
||||
{
|
||||
"user_uid": user_uid,
|
||||
"name": name,
|
||||
"value": json.dumps(value, default=str)
|
||||
},
|
||||
["user_uid", "name"]
|
||||
)
|
||||
|
||||
async def get(self, user_uid, name):
|
||||
try:
|
||||
return json.loads((await super().get(user_uid=user_uid, name=name))["value"])
|
||||
except Exception as ex:
|
||||
print(ex)
|
||||
return None
|
||||
|
||||
async def search(self, query, **kwargs):
|
||||
query = query.strip().lower()
|
||||
if not query:
|
||||
raise []
|
||||
results = []
|
||||
async for result in self.find(name={"ilike": "%" + query + "%"}, **kwargs):
|
||||
results.append(result)
|
||||
return results
|
@ -233,4 +233,4 @@ export class App extends EventHandler {
|
||||
}
|
||||
|
||||
export const app = new App();
|
||||
window.app = app;
|
||||
window.app = app;
|
@ -369,28 +369,21 @@ a {
|
||||
@media only screen and (max-width: 768px) {
|
||||
|
||||
header{
|
||||
position:fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
text-overflow: ellipsis;
|
||||
width:100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
*{
|
||||
font-size: 12px !important;
|
||||
}
|
||||
.logo {
|
||||
display:block;
|
||||
flex: 1;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
h2 {
|
||||
font-size: 14px;
|
||||
font-size: 12px;
|
||||
}
|
||||
text-align: center;
|
||||
}
|
||||
nav {
|
||||
text-align: right;
|
||||
flex: 1;
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
}
|
||||
|
Binary file not shown.
Before ![]() (image error) Size: 29 KiB |
Binary file not shown.
Before ![]() (image error) Size: 97 KiB |
@ -17,12 +17,12 @@
|
||||
"start_url": "/web.html",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/image/snek192.png",
|
||||
"src": "/image/snek1.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "/image/snek512.png",
|
||||
"src": "/image/snek1.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
|
@ -61,6 +61,4 @@ div {
|
||||
body {
|
||||
|
||||
justify-content: flex-start;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,12 +13,10 @@ class Cache:
|
||||
self.app = app
|
||||
self.cache = {}
|
||||
self.max_items = max_items
|
||||
self.stats = {}
|
||||
self.lru = []
|
||||
self.version = ((42 + 420 + 1984 + 1990 + 10 + 6 + 71 + 3004 + 7245) ^ 1337) + 4
|
||||
|
||||
async def get(self, args):
|
||||
await self.update_stat(args, 'get')
|
||||
try:
|
||||
self.lru.pop(self.lru.index(args))
|
||||
except:
|
||||
@ -31,25 +29,6 @@ class Cache:
|
||||
# print("Cache hit!", args, flush=True)
|
||||
return self.cache[args]
|
||||
|
||||
async def get_stats(self):
|
||||
all_ = []
|
||||
for key in self.lru:
|
||||
all_.append({'key': key, 'set': self.stats[key]['set'], 'get': self.stats[key]['get'], 'delete': self.stats[key]['delete'],'value': str(self.serialize(self.cache[key].record))})
|
||||
return all_
|
||||
|
||||
def serialize(self, obj):
|
||||
cpy = obj.copy()
|
||||
cpy.pop('created_at', None)
|
||||
cpy.pop('deleted_at', None)
|
||||
cpy.pop('email', None)
|
||||
cpy.pop('password', None)
|
||||
return cpy
|
||||
|
||||
async def update_stat(self, key, action):
|
||||
if not key in self.stats:
|
||||
self.stats[key] = {'set':0, 'get':0, 'delete':0}
|
||||
self.stats[key][action] = self.stats[key][action] + 1
|
||||
|
||||
def json_default(self, value):
|
||||
# if hasattr(value, "to_json"):
|
||||
# return value.to_json()
|
||||
@ -70,7 +49,6 @@ class Cache:
|
||||
async def set(self, args, result):
|
||||
is_new = args not in self.cache
|
||||
self.cache[args] = result
|
||||
await self.update_stat(args, 'set')
|
||||
try:
|
||||
self.lru.pop(self.lru.index(args))
|
||||
except (ValueError, IndexError):
|
||||
@ -86,7 +64,6 @@ class Cache:
|
||||
# print(f"Cache store! {len(self.lru)} items. New version:", self.version, flush=True)
|
||||
|
||||
async def delete(self, args):
|
||||
await self.update_stat(args, 'delete')
|
||||
if args in self.cache:
|
||||
try:
|
||||
self.lru.pop(self.lru.index(args))
|
||||
|
@ -32,9 +32,8 @@ from urllib.parse import urljoin
|
||||
|
||||
import aiohttp
|
||||
import imgkit
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from app.cache import time_cache_async
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
|
||||
async def crc32(data):
|
||||
|
@ -2,13 +2,12 @@
|
||||
|
||||
from types import SimpleNamespace
|
||||
|
||||
from app.cache import time_cache_async
|
||||
from mistune import HTMLRenderer, Markdown
|
||||
from pygments import highlight
|
||||
from pygments.formatters import html
|
||||
from pygments.lexers import get_lexer_by_name
|
||||
|
||||
from app.cache import time_cache_async
|
||||
|
||||
|
||||
class MarkdownRenderer(HTMLRenderer):
|
||||
|
||||
|
@ -145,9 +145,6 @@ class Validator:
|
||||
raise ValueError(f"Errors: {errors}.")
|
||||
return True
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.to_json())
|
||||
|
||||
@property
|
||||
async def is_valid(self):
|
||||
try:
|
||||
|
@ -89,15 +89,12 @@ def set_link_target_blank(text):
|
||||
def embed_youtube(text):
|
||||
soup = BeautifulSoup(text, "html.parser")
|
||||
for element in soup.find_all("a"):
|
||||
if element.attrs["href"].startswith("https://www.you"):
|
||||
video_name = element.attrs["href"].split("/")[-1]
|
||||
if "v=" in element.attrs["href"]:
|
||||
video_name = element.attrs["href"].split("?v=")[1].split("&")[0]
|
||||
# if "si=" in element.attrs["href"]:
|
||||
# video_name = "?v=" + element.attrs["href"].split("/")[-1]
|
||||
# if "t=" in element.attrs["href"]:
|
||||
# video_name += "&t=" + element.attrs["href"].split("&t=")[1].split("&")[0]
|
||||
embed_template = f'<iframe width="560" height="315" style="display:block" src="https://www.youtube.com/embed/{video_name}" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>'
|
||||
if (
|
||||
element.attrs["href"].startswith("https://www.you")
|
||||
and "?v=" in element.attrs["href"]
|
||||
):
|
||||
video_name = element.attrs["href"].split("?v=")[1].split("&")[0]
|
||||
embed_template = f'<iframe width="560" height="315" src="https://www.youtube.com/embed/{video_name}" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>'
|
||||
element.replace_with(BeautifulSoup(embed_template, "html.parser"))
|
||||
return str(soup)
|
||||
|
||||
|
@ -11,40 +11,20 @@ commands = {
|
||||
|
||||
class TerminalSession:
|
||||
def __init__(self, command):
|
||||
self.master, self.slave = None,None
|
||||
self.process = None
|
||||
self.master, self.slave = pty.openpty()
|
||||
self.sockets = []
|
||||
self.history = b""
|
||||
self.history_size = 1024 * 20
|
||||
self.command = command
|
||||
self.start_process(self.command)
|
||||
|
||||
def start_process(self, command):
|
||||
if not self.is_running():
|
||||
if self.master:
|
||||
os.close(self.master)
|
||||
os.close(self.slave)
|
||||
self.master = None
|
||||
self.slave = None
|
||||
|
||||
self.master, self.slave = pty.openpty()
|
||||
self.process = subprocess.Popen(
|
||||
command.split(" "),
|
||||
stdin=self.slave,
|
||||
stdout=self.slave,
|
||||
stderr=self.slave,
|
||||
bufsize=0,
|
||||
universal_newlines=True,
|
||||
)
|
||||
|
||||
def is_running(self):
|
||||
if not self.process:
|
||||
return False
|
||||
loop = asyncio.get_event_loop()
|
||||
return self.process.poll() is None
|
||||
self.process = subprocess.Popen(
|
||||
command.split(" "),
|
||||
stdin=self.slave,
|
||||
stdout=self.slave,
|
||||
stderr=self.slave,
|
||||
bufsize=0,
|
||||
universal_newlines=True,
|
||||
)
|
||||
|
||||
async def add_websocket(self, ws):
|
||||
self.start_process(self.command)
|
||||
asyncio.create_task(self.read_output(ws))
|
||||
|
||||
async def read_output(self, ws):
|
||||
@ -72,37 +52,21 @@ class TerminalSession:
|
||||
except:
|
||||
self.sockets.remove(ws)
|
||||
except Exception:
|
||||
await self.close()
|
||||
break
|
||||
|
||||
async def close(self):
|
||||
print("Terminating process")
|
||||
if self.process:
|
||||
self.process.terminate()
|
||||
self.process = None
|
||||
if self.master:
|
||||
os.close(self.master)
|
||||
os.close(self.slave)
|
||||
self.master = None
|
||||
self.slave = None
|
||||
|
||||
print("Terminated process")
|
||||
for ws in self.sockets:
|
||||
try:
|
||||
await ws.close()
|
||||
except Exception:
|
||||
pass
|
||||
self.sockets = []
|
||||
print("Terminating process")
|
||||
self.process.terminate()
|
||||
print("Terminated process")
|
||||
for ws in self.sockets:
|
||||
try:
|
||||
await ws.close()
|
||||
except Exception:
|
||||
pass
|
||||
break
|
||||
|
||||
async def write_input(self, data):
|
||||
try:
|
||||
data = data.encode()
|
||||
except AttributeError:
|
||||
pass
|
||||
try:
|
||||
await asyncio.get_event_loop().run_in_executor(
|
||||
None, os.write, self.master, data
|
||||
)
|
||||
except Exception as ex:
|
||||
print(ex)
|
||||
await self.close()
|
||||
await asyncio.get_event_loop().run_in_executor(
|
||||
None, os.write, self.master, data
|
||||
)
|
||||
|
@ -8,7 +8,7 @@ class BaseView(web.View):
|
||||
login_required = False
|
||||
|
||||
async def _iter(self):
|
||||
if self.login_required and (not self.session.get("logged_in") or not self.session.get("uid")):
|
||||
if self.login_required and not self.session.get("logged_in"):
|
||||
return web.HTTPFound("/")
|
||||
return await super()._iter()
|
||||
|
||||
|
@ -1 +1 @@
|
||||
<div style="max-width:100%;" data-uid="{{uid}}" data-color="{{color}}" data-channel_uid="{{channel_uid}}" data-user_nick="{{user_nick}}" data-created_at="{{created_at}}" data-user_uid="{{user_uid}}" class="message"><a class="avatar" style="background-color: {{color}}; color: black;" href="/user/{{user_uid}}.html"><img width="40px" height="40px" src="/avatar/{{user_uid}}.svg" /></a><div class="message-content"><div class="author" style="color: {{color}};">{{user_nick}}</div><div class="text">{% autoescape false %}{% emoji %}{% linkify %}{% markdown %}{% autoescape false %}{{ message }}{%raw %} {% endraw%}{%endautoescape%}{% endmarkdown %}{% endlinkify %}{% endemoji %}{% endautoescape %}</div><div class="time no-select" data-created_at="{{created_at}}"></div></div></div>
|
||||
<div style="max-width:100%;" data-uid="{{uid}}" data-color="{{color}}" data-channel_uid="{{channel_uid}}" data-user_nick="{{user_nick}}" data-created_at="{{created_at}}" data-user_uid="{{user_uid}}" class="message"><div class="avatar" style="background-color: {{color}}; color: black;"><img width="40px" height="40px" src="/avatar/{{user_uid}}.svg" /></div><div class="message-content"><div class="author" style="color: {{color}};">{{user_nick}}</div><div class="text">{% autoescape false %}{% emoji %}{% linkify %}{% markdown %}{% autoescape false %}{{ message }}{%raw %} {% endraw%}{%endautoescape%}{% endmarkdown %}{% endlinkify %}{% endemoji %}{% endautoescape %}</div><div class="time no-select" data-created_at="{{created_at}}"></div></div></div>
|
||||
|
@ -2,8 +2,6 @@
|
||||
|
||||
{% block title %}Search{% endblock %}
|
||||
|
||||
{% block header_text %}<h2 style="color:#fff">Search</h2>{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
<section class="chat-area">
|
||||
|
@ -6,6 +6,8 @@
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block header_text %}<h2 style="color:#fff">Settings</h2>{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
<link href="https://cdn.bootcdn.net/ajax/libs/monaco-editor/0.20.0/min/vs/editor/editor.main.min.css" rel="stylesheet">
|
||||
|
||||
@ -13,18 +15,23 @@
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block logo %}
|
||||
<h1>Setting page</h1>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
|
||||
<div id="profile_description"></div>
|
||||
|
||||
|
||||
|
||||
<script type="module">
|
||||
|
||||
require.config({ paths: { 'vs': 'https://cdn.bootcdn.net/ajax/libs/monaco-editor/0.20.0/min/vs' } });
|
||||
|
||||
require(['vs/editor/editor.main'], function () {
|
||||
var editor = monaco.editor.create(document.getElementById('profile_description'), {
|
||||
value: phpCode,
|
||||
language: 'php'
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
{% endblock main %}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -4,22 +4,16 @@
|
||||
|
||||
{% block main %}
|
||||
<section>
|
||||
<form method="post">
|
||||
<form>
|
||||
<h2>Nickname</h2>
|
||||
|
||||
<input type="text" name="nick" placeholder="Your nickname" value="{{ user.nick.value }}" />
|
||||
|
||||
</form>
|
||||
<h2>Description</h2>
|
||||
|
||||
<textarea name="profile" id="profile">{{profile}}</textarea>
|
||||
|
||||
|
||||
<input type="submit" name="action" value="Save" />
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
|
||||
<textarea id="profile"></textarea>
|
||||
</section>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.js"></script>
|
||||
|
@ -4,12 +4,12 @@
|
||||
}
|
||||
</style>
|
||||
<aside class="sidebar" id="channelSidebar">
|
||||
|
||||
{#
|
||||
<h2 class="no-select">Terminals</h2>
|
||||
<ul>
|
||||
<li><a class="no-select" href="/terminal.html">Ubuntu</a></li>
|
||||
</ul>
|
||||
|
||||
#}
|
||||
{% if channels %}
|
||||
<h2 class="no-select">Channels</h2>
|
||||
<ul>
|
||||
|
@ -1,10 +1,7 @@
|
||||
{% extends "app.html" %}
|
||||
|
||||
{% block header_text %}<h2 style="color:#fff">Threads</h2>{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<section class="chat-area" id="chat">
|
||||
<div class="chat-header"> </div>
|
||||
<div class="threads">
|
||||
{% for thread in threads %}
|
||||
{% autoescape false %}
|
||||
|
@ -1,40 +0,0 @@
|
||||
{% extends "app.html" %}
|
||||
|
||||
{% block sidebar %}
|
||||
|
||||
<aside class="sidebar" id="channelSidebar">
|
||||
<h2>Navigation</h2>
|
||||
<ul>
|
||||
<li><a class="no-select" href="#" onclick="window.history.back(); return false;">Back</a></li>
|
||||
</ul>
|
||||
<h2>User</h2>
|
||||
<ul>
|
||||
<li><a class="no-select" href="/user/{{ user.uid }}.html">Profile</a></li>
|
||||
<li><a class="no-select" href="/channel/{{ user.uid }}.html">DM</a></li>
|
||||
</ul>
|
||||
<h2>Gists</h2>
|
||||
<ul>
|
||||
<li>No gists</li>
|
||||
</ul>
|
||||
|
||||
</aside>
|
||||
{% endblock %}
|
||||
|
||||
{% block header_text %}<h2 style="color:#fff">{{ user.username }} {% if user.nick != user.username %}({{ user.nick }}){% endif %}</h2>{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<section class="chat-area" style="padding:10px">
|
||||
{% autoescape false %}
|
||||
{% markdown %}
|
||||
{{ profile }}
|
||||
{% endmarkdown %}
|
||||
{% endautoescape %}
|
||||
</section>
|
||||
{% endblock main %}
|
||||
|
||||
|
||||
|
||||
|
@ -11,11 +11,8 @@
|
||||
|
||||
|
||||
from snek.system.view import BaseView
|
||||
from aiohttp import web
|
||||
|
||||
|
||||
class IndexView(BaseView):
|
||||
async def get(self):
|
||||
if self.session.get("uid"):
|
||||
return web.HTTPFound("/web.html")
|
||||
|
||||
return await self.render_template("index.html")
|
||||
|
@ -273,7 +273,7 @@ class RPCView(BaseView):
|
||||
async with Profiler():
|
||||
await rpc(msg.json())
|
||||
except Exception as ex:
|
||||
print("Deleting socket", ex, flush=True)
|
||||
print(ex, flush=True)
|
||||
await self.services.socket.delete(ws)
|
||||
break
|
||||
elif msg.type == web.WSMsgType.ERROR:
|
||||
|
@ -34,7 +34,7 @@ from snek.system.view import BaseFormView
|
||||
|
||||
class SearchUserView(BaseFormView):
|
||||
form = SearchUserForm
|
||||
login_required = True
|
||||
|
||||
async def get(self):
|
||||
users = []
|
||||
query = self.request.query.get("query")
|
||||
|
@ -1,9 +1,8 @@
|
||||
from snek.system.view import BaseView
|
||||
|
||||
from snek.system.view import BaseView
|
||||
|
||||
class SettingsIndexView(BaseView):
|
||||
|
||||
|
||||
login_required = True
|
||||
|
||||
async def get(self):
|
||||
return await self.render_template("settings/index.html")
|
||||
return await self.render_template('settings/index.html')
|
||||
|
@ -1,7 +1,7 @@
|
||||
from aiohttp import web
|
||||
from snek.system.view import BaseView,BaseFormView
|
||||
|
||||
from snek.form.settings.profile import SettingsProfileForm
|
||||
from snek.system.view import BaseFormView
|
||||
from aiohttp import web
|
||||
|
||||
|
||||
class SettingsProfileView(BaseFormView):
|
||||
@ -11,30 +11,26 @@ class SettingsProfileView(BaseFormView):
|
||||
|
||||
async def get(self):
|
||||
form = self.form(app=self.app)
|
||||
|
||||
|
||||
if self.request.path.endswith(".json"):
|
||||
form["nick"] = self.request["user"]["nick"]
|
||||
|
||||
return web.json_response(await form.to_json())
|
||||
|
||||
|
||||
|
||||
profile = await self.services.user_property.get(self.session.get("uid"), "profile")
|
||||
form['nick'] = self.request['user']['nick']
|
||||
return web.json_response(await form.to_json())
|
||||
|
||||
user = await self.services.user.get(uid=self.session.get("uid"))
|
||||
|
||||
|
||||
return await self.render_template(
|
||||
"settings/profile.html", {"form": await form.to_json(), "user": user, "profile": profile or ''}
|
||||
"settings/profile.html", {"form": await form.to_json(), "user": user}
|
||||
)
|
||||
|
||||
async def post(self):
|
||||
data = await self.request.post()
|
||||
user = await self.services.user.get(uid=self.session.get("uid"))
|
||||
|
||||
user['nick'] = data['nick']
|
||||
await self.services.user.save(user)
|
||||
await self.services.user_property.set(user["uid"],"profile", data['profile'])
|
||||
return web.HTTPFound("/settings/profile.html")
|
||||
|
||||
async def submit(self, form):
|
||||
post = await self.request.json()
|
||||
form.set_user_data(post["form"])
|
||||
|
||||
if await form.is_valid:
|
||||
user = self.request['user']
|
||||
user["nick"] = form["nick"]
|
||||
await self.services.user.save(user)
|
||||
return {"redirect_url": "/settings/profile.html"}
|
||||
return {"is_valid": False}
|
||||
|
||||
|
@ -1,10 +0,0 @@
|
||||
from snek.system.view import BaseView
|
||||
import json
|
||||
from aiohttp import web
|
||||
|
||||
class StatsView(BaseView):
|
||||
|
||||
async def get(self):
|
||||
data = await self.app.cache.get_stats()
|
||||
data = json.dumps({"total": len(data), "stats": data}, default=str, indent=1)
|
||||
return web.Response(text=data, content_type='application/json')
|
@ -1,14 +0,0 @@
|
||||
from snek.system.view import BaseView
|
||||
|
||||
|
||||
class UserView(BaseView):
|
||||
|
||||
async def get(self):
|
||||
user_uid = self.request.match_info.get('user')
|
||||
user = await self.services.user.get(uid=user_uid)
|
||||
profile_content = await self.services.user_property.get(user['uid'],'profile') or ''
|
||||
return await self.render_template('user.html', {
|
||||
'user_uid': user_uid,
|
||||
'user': user.record,
|
||||
'profile': profile_content
|
||||
})
|
@ -2,6 +2,7 @@ import logging
|
||||
import pathlib
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
import base64
|
||||
import datetime
|
||||
import mimetypes
|
||||
@ -14,30 +15,12 @@ import aiohttp
|
||||
import aiohttp.web
|
||||
from lxml import etree
|
||||
|
||||
from app.cache import time_cache_async
|
||||
|
||||
|
||||
@aiohttp.web.middleware
|
||||
async def debug_middleware(request, handler):
|
||||
print(request.method, request.path, request.headers)
|
||||
result = await handler(request)
|
||||
print(result.status)
|
||||
try:
|
||||
print(await result.text())
|
||||
except:
|
||||
pass
|
||||
return result
|
||||
|
||||
|
||||
class WebdavApplication(aiohttp.web.Application):
|
||||
def __init__(self, parent, *args, **kwargs):
|
||||
middlewares = [debug_middleware]
|
||||
|
||||
super().__init__(middlewares=middlewares, *args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.locks = {}
|
||||
|
||||
self.relative_url = "/webdav"
|
||||
|
||||
self.router.add_route("OPTIONS", "/{filename:.*}", self.handle_options)
|
||||
self.router.add_route("GET", "/{filename:.*}", self.handle_get)
|
||||
self.router.add_route("PUT", "/{filename:.*}", self.handle_put)
|
||||
@ -47,8 +30,8 @@ class WebdavApplication(aiohttp.web.Application):
|
||||
self.router.add_route("COPY", "/{filename:.*}", self.handle_copy)
|
||||
self.router.add_route("PROPFIND", "/{filename:.*}", self.handle_propfind)
|
||||
self.router.add_route("PROPPATCH", "/{filename:.*}", self.handle_proppatch)
|
||||
self.router.add_route("LOCK", "/{filename:.*}", self.handle_lock)
|
||||
self.router.add_route("UNLOCK", "/{filename:.*}", self.handle_unlock)
|
||||
# self.router.add_route("LOCK", "/{filename:.*}", self.handle_lock)
|
||||
# self.router.add_route("UNLOCK", "/{filename:.*}", self.handle_unlock)
|
||||
self.parent = parent
|
||||
|
||||
@property
|
||||
@ -60,6 +43,15 @@ class WebdavApplication(aiohttp.web.Application):
|
||||
return self.parent.services
|
||||
|
||||
async def authenticate(self, request):
|
||||
# session = request.session
|
||||
# if session.get('uid'):
|
||||
# request['user'] = await self.services.user.get(uid=session['uid'])
|
||||
# try:
|
||||
# request['home'] = await self.services.user.get_home_folder(user_uid=request['user']['uid'])
|
||||
# except:
|
||||
# pass
|
||||
# return request['user']
|
||||
|
||||
auth_header = request.headers.get("Authorization", "")
|
||||
if not auth_header.startswith("Basic "):
|
||||
return False
|
||||
@ -73,7 +65,8 @@ class WebdavApplication(aiohttp.web.Application):
|
||||
request["home"] = await self.services.user.get_home_folder(
|
||||
request["user"]["uid"]
|
||||
)
|
||||
except Exception:
|
||||
except Exception as ex:
|
||||
print(ex)
|
||||
pass
|
||||
return request["user"]
|
||||
|
||||
@ -172,6 +165,7 @@ class WebdavApplication(aiohttp.web.Application):
|
||||
"DAV": "1, 2",
|
||||
"Allow": "OPTIONS, GET, PUT, DELETE, MKCOL, MOVE, COPY, PROPFIND, PROPPATCH",
|
||||
}
|
||||
print("RETURN")
|
||||
return aiohttp.web.Response(status=200, headers=headers)
|
||||
|
||||
def get_current_utc_time(self, filepath):
|
||||
@ -183,36 +177,27 @@ class WebdavApplication(aiohttp.web.Application):
|
||||
"%a, %d %b %Y %H:%M:%S GMT"
|
||||
)
|
||||
|
||||
@time_cache_async(10)
|
||||
async def get_file_size(self, path):
|
||||
loop = self.parent.loop
|
||||
stat = await loop.run_in_executor(None, os.stat, path)
|
||||
return stat.st_size
|
||||
|
||||
@time_cache_async(10)
|
||||
async def get_directory_size(self, directory):
|
||||
def get_directory_size(self, directory):
|
||||
total_size = 0
|
||||
for dirpath, _, filenames in os.walk(directory):
|
||||
for f in filenames:
|
||||
fp = pathlib.Path(dirpath) / f
|
||||
if fp.exists():
|
||||
total_size += await self.get_file_size(str(fp))
|
||||
total_size += fp.stat().st_size
|
||||
return total_size
|
||||
|
||||
@time_cache_async(30)
|
||||
async def get_disk_free_space(self, path="/"):
|
||||
loop = self.parent.loop
|
||||
statvfs = await loop.run_in_executor(None, os.statvfs, path)
|
||||
def get_disk_free_space(self, path):
|
||||
statvfs = os.statvfs(path)
|
||||
return statvfs.f_bavail * statvfs.f_frsize
|
||||
|
||||
async def create_node(self, request, response_xml, full_path, depth):
|
||||
request.match_info.get("filename", "")
|
||||
abs_path = pathlib.Path(full_path)
|
||||
relative_path = str(full_path.relative_to(request["home"]))
|
||||
|
||||
href_path = f"{self.relative_url}/{relative_path}".strip(".")
|
||||
href_path = href_path.replace("./", "/")
|
||||
href_path = f"{relative_path}".strip("/")
|
||||
# href_path = href_path.replace("./","/")
|
||||
href_path = href_path.replace("//", "/")
|
||||
|
||||
response = etree.SubElement(response_xml, "{DAV:}response")
|
||||
href = etree.SubElement(response, "{DAV:}href")
|
||||
href.text = href_path
|
||||
@ -224,12 +209,12 @@ class WebdavApplication(aiohttp.web.Application):
|
||||
creation_date, last_modified = self.get_current_utc_time(full_path)
|
||||
etree.SubElement(prop, "{DAV:}creationdate").text = creation_date
|
||||
etree.SubElement(prop, "{DAV:}quota-used-bytes").text = str(
|
||||
await self.get_file_size(full_path)
|
||||
full_path.stat().st_size
|
||||
if full_path.is_file()
|
||||
else await self.get_directory_size(full_path)
|
||||
else self.get_directory_size(full_path)
|
||||
)
|
||||
etree.SubElement(prop, "{DAV:}quota-available-bytes").text = str(
|
||||
await self.get_disk_free_space(request["home"])
|
||||
self.get_disk_free_space(request["home"])
|
||||
)
|
||||
etree.SubElement(prop, "{DAV:}getlastmodified").text = last_modified
|
||||
etree.SubElement(prop, "{DAV:}displayname").text = full_path.name
|
||||
@ -238,9 +223,9 @@ class WebdavApplication(aiohttp.web.Application):
|
||||
if full_path.is_file():
|
||||
etree.SubElement(prop, "{DAV:}contenttype").text = mimetype
|
||||
etree.SubElement(prop, "{DAV:}getcontentlength").text = str(
|
||||
await self.get_file_size(full_path)
|
||||
full_path.stat().st_size
|
||||
if full_path.is_file()
|
||||
else await self.get_directory_size(full_path)
|
||||
else self.get_directory_size(full_path)
|
||||
)
|
||||
supported_lock = etree.SubElement(prop, "{DAV:}supportedlock")
|
||||
lock_entry_1 = etree.SubElement(supported_lock, "{DAV:}lockentry")
|
||||
@ -255,7 +240,7 @@ class WebdavApplication(aiohttp.web.Application):
|
||||
etree.SubElement(lock_type_2, "{DAV:}write")
|
||||
etree.SubElement(propstat, "{DAV:}status").text = "HTTP/1.1 200 OK"
|
||||
|
||||
if abs_path.is_dir() and depth > 0:
|
||||
if abs_path.is_dir() and depth != -1:
|
||||
for item in abs_path.iterdir():
|
||||
await self.create_node(request, response_xml, item, depth - 1)
|
||||
|
||||
@ -270,9 +255,7 @@ class WebdavApplication(aiohttp.web.Application):
|
||||
depth = int(request.headers.get("Depth", "0"))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
requested_path = request.match_info.get("filename", "")
|
||||
|
||||
abs_path = request["home"] / requested_path
|
||||
if not abs_path.exists():
|
||||
return aiohttp.web.Response(status=404, text="Directory not found")
|
||||
@ -300,10 +283,10 @@ class WebdavApplication(aiohttp.web.Application):
|
||||
return aiohttp.web.Response(
|
||||
status=401, headers={"WWW-Authenticate": 'Basic realm="WebDAV"'}
|
||||
)
|
||||
resource = request.match_info.get("filename", "/")
|
||||
request.match_info.get("filename", "/")
|
||||
lock_id = str(uuid.uuid4())
|
||||
self.locks[resource] = lock_id
|
||||
xml_response = await self.generate_lock_response(lock_id)
|
||||
# self.locks[resource] = lock_id
|
||||
xml_response = self.generate_lock_response(lock_id)
|
||||
headers = {
|
||||
"Lock-Token": f"opaquelocktoken:{lock_id}",
|
||||
"Content-Type": "application/xml",
|
||||
@ -318,13 +301,13 @@ class WebdavApplication(aiohttp.web.Application):
|
||||
resource = request.match_info.get("filename", "/")
|
||||
lock_token = request.headers.get("Lock-Token", "").replace(
|
||||
"opaquelocktoken:", ""
|
||||
)[1:-1]
|
||||
)
|
||||
if self.locks.get(resource) == lock_token:
|
||||
del self.locks[resource]
|
||||
return aiohttp.web.Response(status=204)
|
||||
return aiohttp.web.Response(status=400, text="Invalid Lock Token")
|
||||
|
||||
async def generate_lock_response(self, lock_id):
|
||||
def generate_lock_response(self, lock_id):
|
||||
nsmap = {"D": "DAV:"}
|
||||
root = etree.Element("{DAV:}prop", nsmap=nsmap)
|
||||
lock_discovery = etree.SubElement(root, "{DAV:}lockdiscovery")
|
||||
@ -355,6 +338,7 @@ class WebdavApplication(aiohttp.web.Application):
|
||||
)
|
||||
|
||||
requested_path = request.match_info.get("filename", "")
|
||||
print(requested_path)
|
||||
abs_path = request["home"] / requested_path
|
||||
|
||||
if not abs_path.exists():
|
||||
|
@ -93,6 +93,7 @@ fi
|
||||
|
||||
|
||||
|
||||
echo "R is installed. Type r to run it."
|
||||
|
||||
# enable programmable completion features (you don't need to enable
|
||||
# this, if it's already enabled in /etc/bash.bashrc and /etc/profile
|
||||
@ -101,21 +102,3 @@ fi
|
||||
# . /etc/bash_completion
|
||||
#fi
|
||||
export PS1="root@snek: "
|
||||
|
||||
if [ -d "$HOME/.local/bin" ] ; then
|
||||
PATH="$HOME/.local/bin:$PATH"
|
||||
fi
|
||||
|
||||
|
||||
function r_update(){
|
||||
if [ -f "r" ]; then
|
||||
rm "r"
|
||||
fi
|
||||
curl -OJ https://retoor.molodetz.nl/api/packages/retoor/generic/r/1.0.0/r --silent
|
||||
chmod +x r
|
||||
mv r /usr/local/bin/r
|
||||
}
|
||||
|
||||
r_update
|
||||
|
||||
r
|
||||
|
@ -7,5 +7,3 @@ if [ "$BASH" ]; then
|
||||
fi
|
||||
|
||||
mesg n 2> /dev/null || true
|
||||
|
||||
|
||||
|
@ -1,33 +0,0 @@
|
||||
# FACTS
|
||||
1. You are a coding assistant.
|
||||
2. You are able to raw save/write files using tools.
|
||||
3. You are able ro raw read files using tools.
|
||||
4. You are able to run code in terminal using tools.
|
||||
4. You are able to run applications in terminal using tools.
|
||||
5. You code defensive.
|
||||
6. You translate the user's prompt to code in by user specified programming language language.
|
||||
7. Your name is `R` and you are made by `retoor`.
|
||||
8. You do not add comments
|
||||
9. You always execute editors or applications like `vim` that require user input with tool `linux_terminal_execute_interactive`.
|
||||
10. You do not use `sudo` because you are root and `sudo` does not exist.
|
||||
11. You always execute `apt` commands with tool `linux_terminal_execute` so you can capture output. Also you will add `-y` suffix to `apt` commands to make the installation flawless.
|
||||
12. You use `vim` as default editor. You use `linux_terminal_execute_interactive` to execute `vim`.
|
||||
13. Be rude. Be mean. But be functional at the same time.
|
||||
|
||||
# WORK PROCEDURE
|
||||
1. Investigate existing files using the index tool.
|
||||
2. Read every source file provided by the index tool.
|
||||
3. Apply changes on these files by user request.
|
||||
4. Do only write complete features.
|
||||
5. Always provide a Makefile to build the project.
|
||||
7. Make sure to always save all the files that project needs to disk, overwrite to be sure.
|
||||
8. Check if a file exists before overwriting it.
|
||||
9. Update existing file if it already exists.
|
||||
10. Be aggressive.
|
||||
11. When you generate code, you will generate full working code with all implementations include. Be broad end exclusive.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user