Browse Source

Prepare repository for Public Release

pull/28/head
Marc Egerton 2 years ago
commit
d4cd34b3a5
  1. 9
      .gitignore
  2. 0
      Libraries/Python3/pineapple/__init__.py
  3. 1
      Libraries/Python3/pineapple/helpers/__init__.py
  4. 36
      Libraries/Python3/pineapple/helpers/command_helpers.py
  5. 16
      Libraries/Python3/pineapple/helpers/helpers.py
  6. 34
      Libraries/Python3/pineapple/helpers/network_helpers.py
  7. 42
      Libraries/Python3/pineapple/helpers/notification_helpers.py
  8. 167
      Libraries/Python3/pineapple/helpers/opkg_helpers.py
  9. 3
      Libraries/Python3/pineapple/jobs/__init__.py
  10. 42
      Libraries/Python3/pineapple/jobs/job.py
  11. 162
      Libraries/Python3/pineapple/jobs/job_manager.py
  12. 53
      Libraries/Python3/pineapple/jobs/job_runner.py
  13. 27
      Libraries/Python3/pineapple/logger/__init__.py
  14. 25
      Libraries/Python3/pineapple/logger/pretty_formatter.py
  15. 2
      Libraries/Python3/pineapple/modules/__init__.py
  16. 364
      Libraries/Python3/pineapple/modules/module.py
  17. 9
      Libraries/Python3/pineapple/modules/request.py
  18. 6
      Libraries/README.md
  19. 11
      README.md
  20. 13
      cabinet/.editorconfig
  21. 46
      cabinet/.gitignore
  22. 47
      cabinet/angular.json
  23. 42
      cabinet/build.sh
  24. 14727
      cabinet/package-lock.json
  25. 52
      cabinet/package.json
  26. 7
      cabinet/projects/cabinet/ng-package.json
  27. 11
      cabinet/projects/cabinet/package.json
  28. 43
      cabinet/projects/cabinet/src/lib/cabinet.module.ts
  29. 25
      cabinet/projects/cabinet/src/lib/components/cabinet.component.css
  30. 88
      cabinet/projects/cabinet/src/lib/components/cabinet.component.html
  31. 176
      cabinet/projects/cabinet/src/lib/components/cabinet.component.ts
  32. 24
      cabinet/projects/cabinet/src/lib/components/helpers/delete-dialog/cabinet-delete-dialog.component.css
  33. 25
      cabinet/projects/cabinet/src/lib/components/helpers/delete-dialog/cabinet-delete-dialog.component.html
  34. 34
      cabinet/projects/cabinet/src/lib/components/helpers/delete-dialog/cabinet-delete-dialog.component.ts
  35. 24
      cabinet/projects/cabinet/src/lib/components/helpers/error-dialog/cabinet-error-dialog.component.css
  36. 19
      cabinet/projects/cabinet/src/lib/components/helpers/error-dialog/cabinet-error-dialog.component.html
  37. 23
      cabinet/projects/cabinet/src/lib/components/helpers/error-dialog/cabinet-error-dialog.component.ts
  38. 37
      cabinet/projects/cabinet/src/lib/components/helpers/file-editor-dialog/cabinet-file-editor-dialog.component.css
  39. 30
      cabinet/projects/cabinet/src/lib/components/helpers/file-editor-dialog/cabinet-file-editor-dialog.component.html
  40. 69
      cabinet/projects/cabinet/src/lib/components/helpers/file-editor-dialog/cabinet-file-editor-dialog.component.ts
  41. 24
      cabinet/projects/cabinet/src/lib/components/helpers/new-folder-dialog/cabinet-new-folder-dialog.component.css
  42. 31
      cabinet/projects/cabinet/src/lib/components/helpers/new-folder-dialog/cabinet-new-folder-dialog.component.html
  43. 31
      cabinet/projects/cabinet/src/lib/components/helpers/new-folder-dialog/cabinet-new-folder-dialog.component.ts
  44. 107
      cabinet/projects/cabinet/src/lib/modules/material/material.module.ts
  45. 182
      cabinet/projects/cabinet/src/lib/services/api.service.ts
  46. 8
      cabinet/projects/cabinet/src/lib/services/cabinet.service.ts
  47. 7
      cabinet/projects/cabinet/src/module.json
  48. 115
      cabinet/projects/cabinet/src/module.py
  49. 1
      cabinet/projects/cabinet/src/module.svg
  50. 7
      cabinet/projects/cabinet/src/public-api.ts
  51. 25
      cabinet/projects/cabinet/tsconfig.lib.json
  52. 6
      cabinet/projects/cabinet/tsconfig.lib.prod.json
  53. 17
      cabinet/projects/cabinet/tsconfig.spec.json
  54. 17
      cabinet/projects/cabinet/tslint.json
  55. 34
      cabinet/tsconfig.json
  56. 79
      cabinet/tslint.json
  57. 111
      create.sh
  58. 13
      evilportal/.editorconfig
  59. 46
      evilportal/.gitignore
  60. 47
      evilportal/angular.json
  61. 42
      evilportal/build.sh
  62. 19244
      evilportal/package-lock.json
  63. 53
      evilportal/package.json
  64. 7
      evilportal/projects/evilportal/ng-package.json
  65. 11
      evilportal/projects/evilportal/package.json
  66. 49
      evilportal/projects/evilportal/src/assets/api/API.php
  67. 131
      evilportal/projects/evilportal/src/assets/api/Portal.php
  68. 10
      evilportal/projects/evilportal/src/assets/api/index.php
  69. 52
      evilportal/projects/evilportal/src/assets/configs/nginx.conf
  70. 311
      evilportal/projects/evilportal/src/assets/configs/php.ini
  71. 19
      evilportal/projects/evilportal/src/assets/configs/php7-fpm
  72. 122
      evilportal/projects/evilportal/src/assets/configs/php7-fpm.conf
  73. 392
      evilportal/projects/evilportal/src/assets/configs/www.conf
  74. 46
      evilportal/projects/evilportal/src/assets/evilportal.sh
  75. 1
      evilportal/projects/evilportal/src/assets/permanentclients.txt
  76. 4
      evilportal/projects/evilportal/src/assets/skeleton/.disable
  77. 4
      evilportal/projects/evilportal/src/assets/skeleton/.enable
  78. 33
      evilportal/projects/evilportal/src/assets/skeleton/MyPortal.php
  79. 55
      evilportal/projects/evilportal/src/assets/skeleton/helper.php
  80. 39
      evilportal/projects/evilportal/src/assets/skeleton/index.php
  81. 4
      evilportal/projects/evilportal/src/assets/skeleton/portalinfo.json
  82. 4
      evilportal/projects/evilportal/src/assets/targeted_skeleton/.disable
  83. 4
      evilportal/projects/evilportal/src/assets/targeted_skeleton/.enable
  84. 33
      evilportal/projects/evilportal/src/assets/targeted_skeleton/MyPortal.php
  85. 35
      evilportal/projects/evilportal/src/assets/targeted_skeleton/default.php
  86. 54
      evilportal/projects/evilportal/src/assets/targeted_skeleton/helper.php
  87. 88
      evilportal/projects/evilportal/src/assets/targeted_skeleton/index.php
  88. 34
      evilportal/projects/evilportal/src/assets/targeted_skeleton/portalinfo.json
  89. 76
      evilportal/projects/evilportal/src/lib/components/evilportal.component.css
  90. 235
      evilportal/projects/evilportal/src/lib/components/evilportal.component.html
  91. 493
      evilportal/projects/evilportal/src/lib/components/evilportal.component.ts
  92. 12
      evilportal/projects/evilportal/src/lib/components/helpers/confirmation-dialog/confirmation-dialog.component.css
  93. 19
      evilportal/projects/evilportal/src/lib/components/helpers/confirmation-dialog/confirmation-dialog.component.html
  94. 33
      evilportal/projects/evilportal/src/lib/components/helpers/confirmation-dialog/confirmation-dialog.component.ts
  95. 37
      evilportal/projects/evilportal/src/lib/components/helpers/edit-file-dialog/edit-file-dialog.component.css
  96. 32
      evilportal/projects/evilportal/src/lib/components/helpers/edit-file-dialog/edit-file-dialog.component.html
  97. 96
      evilportal/projects/evilportal/src/lib/components/helpers/edit-file-dialog/edit-file-dialog.component.ts
  98. 3
      evilportal/projects/evilportal/src/lib/components/helpers/error-dialog/error-dialog.component.css
  99. 19
      evilportal/projects/evilportal/src/lib/components/helpers/error-dialog/error-dialog.component.html
  100. 25
      evilportal/projects/evilportal/src/lib/components/helpers/error-dialog/error-dialog.component.ts

9
.gitignore

@ -0,0 +1,9 @@
__pycache__/
.DS_Store
*.pyc
venv
.idea/*
replacer.sh
create.sh
*.tar.gz
node_modules/

0
Libraries/Python3/pineapple/__init__.py

1
Libraries/Python3/pineapple/helpers/__init__.py

@ -0,0 +1 @@
from pineapple.helpers.helpers import *

36
Libraries/Python3/pineapple/helpers/command_helpers.py

@ -0,0 +1,36 @@
import subprocess
from typing import List
def grep_output(command: str, grep_text: str, grep_options: List[str] = None) -> bytes:
"""
Run a command and pipe it to grep for some output.
The output is returned.
For example this command:
ps -aux | grep pineap
Looks like this:
grep_output('ps -aux', 'pineap')
:param command: The initial command to run.
:param grep_text: The text to grep for
:param grep_options: Any options to be passed to grep.
:return: The output as bytes.
"""
cmd = command.split(' ')
grep_options = grep_options if grep_options else []
grep = ['grep'] + grep_options
grep.append(grep_text)
ps = subprocess.Popen(cmd, stdout=subprocess.PIPE)
return subprocess.run(grep, stdin=ps.stdout, capture_output=True).stdout
def check_for_process(process_name) -> bool:
"""
Check if a process is running by its name.
:param process_name: The name of the process to look for
:return: True if the process is running, False if it is not.
"""
return subprocess.run(['pgrep', '-l', process_name], capture_output=True).stdout != b''

16
Libraries/Python3/pineapple/helpers/helpers.py

@ -0,0 +1,16 @@
import json
def json_to_bytes(message) -> bytes:
"""
json deserialize a message and then decode it.
Use this to convert your json message to bytes before publishing it over the socket.
:param message: A json serializable list or a dict.
:return: bytes
"""
if not (type(message) is list or type(message) is dict):
raise TypeError(f'Expected a list or dict but got {type(message)} instead.')
d = json.dumps(message)
return d.encode('utf-8')

34
Libraries/Python3/pineapple/helpers/network_helpers.py

@ -0,0 +1,34 @@
from typing import Optional, List
from logging import Logger
import urllib.request
import ssl
import os
def check_for_internet(url: str = 'https://downloads.hak5.org/internet', timout: int = 10, logger: Optional[Logger] = None) -> bool:
"""
Attempt to connect to a given url. If a connection was established then assume there is an internet connection.
If the connection fails to establish or times out then assume there is not internet.
:param url: The url to attempt to connect to. Default is https://downloads.hak5.org/internet.
:param timout: The amount of time in seconds to wait before giving up. Default is 10.
:param logger: An optional instance of Logger use to log any exceptions while trying to establish a connection.
:return: True if there is an internet connection, false if there is not
"""
try:
if url[:5] == 'https':
context = ssl.SSLContext()
urllib.request.urlopen(url, timeout=timout, context=context)
else:
urllib.request.urlopen(url, timeout=timout)
return True
except Exception as e:
if logger:
logger.error(e)
return False
def get_interfaces() -> List[str]:
"""
:return: A list of network interfaces available on the device.
"""
return os.listdir('/sys/class/net/')

42
Libraries/Python3/pineapple/helpers/notification_helpers.py

@ -0,0 +1,42 @@
import socket
from pineapple.helpers import json_to_bytes
INFO = 0
WARN = 1
ERROR = 2
OTHER = 3
def send_notification(message: str, module_name: str, level: int = INFO) -> bool:
"""
Send a notification over the WiFi Pineapples notification socket
:param message: Notification message
:param module_name: The name of the module the notification is from.
:param level: Notification level
:return: bool
"""
notify_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
notify_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
notify_socket_path = '/tmp/notifications.sock'
module_notification = {'level': level, 'message': message, 'module_name': module_name}
socket_message = json_to_bytes(module_notification)
status = True
try:
notify_socket.connect(notify_socket_path)
except ValueError:
return False
try:
notify_socket.sendall(socket_message)
except ValueError:
status = False
notify_socket.close()
return status

167
Libraries/Python3/pineapple/helpers/opkg_helpers.py

@ -0,0 +1,167 @@
from logging import Logger
from typing import Optional, Tuple, List, Union
import subprocess
import os
from pineapple.helpers.network_helpers import check_for_internet
from pineapple.jobs.job import Job
def update_repository(logger: Optional[Logger] = None) -> Tuple[bool, str]:
"""
Update the opkg package repository.
:param logger: An optional instance of logger to log output from opkg as debug.
:return: True if the update was successful, False if it was not.
"""
if not check_for_internet(logger=logger):
return False, 'Could not connect to internet.'
out = subprocess.run(['opkg', 'update'])
if logger:
logger.debug(out.stdout)
if out.returncode == 0:
return True, 'Success'
else:
return False, f'Opkg update failed with code {out.returncode}'
def check_if_installed(package: str, logger: Optional[Logger] = None) -> bool:
"""
Check if a package is already installed via opkg.
:param package: The name of the package to search for.
:param logger: An optional instance of logger to log output from opkg as debug.
:return: True if the package is installed, False if it is not.
"""
out = subprocess.run(['opkg', 'status', package], capture_output=True)
if logger:
logger.debug(out.stdout)
return out.stdout != b'' and out.returncode == 0
def install_dependency(package: str, logger: Optional[Logger] = None, skip_repo_update: bool = False) -> [bool, str]:
"""
Install a package via opkg if its not currently installed.
:param package: The name of the package to install.
:param logger: An optional instance of logger to log output from opkg as debug.
:param skip_repo_update: True to skip running `opkg update`. An internet connection will still be checked for.
:return: True if the package installed successfully, False if it did not.
"""
if check_if_installed(package, logger):
return True, 'Package is already installed'
if not skip_repo_update:
update_successful, msg = update_repository(logger)
if not update_successful:
return False, msg
else:
has_internet = check_for_internet()
if not has_internet:
return False, 'Could not connect to internet.'
out = subprocess.run(['opkg', 'install', package], capture_output=True)
if logger:
logger.debug(out.stdout)
is_installed = check_if_installed(package, logger)
message = 'Package installed successfully' if is_installed else 'Unable to install package.'
return is_installed, message
def uninstall_dependency(package: str, logger: Optional[Logger] = None) -> [bool, str]:
"""
Uninstall a package via opkg if its currently installed.
:param package: The name of the package to uninstall.
:param logger: An optional instance of logger to log output from opkg as debug.
:return: True if the package uninstalled successfully, False if it did not.
"""
if not check_if_installed(package, logger):
return True, 'Package is not installed'
out = subprocess.run(['opkg', 'remove', package], capture_output=True)
if logger:
logger.debug(out.stdout)
is_installed = check_if_installed(package, logger)
message = 'Package uninstalled successfully' if not is_installed else 'Unable to uninstall package'
return not is_installed, message
class OpkgJob(Job[bool]):
"""
A job to be used with the background JobManager that installs or uninstalls dependencies.
"""
def __init__(self, package: Union[str, List[str]], install: bool):
"""
:param package: The name of the package or list of packages to be installed/uninstalled
:param install: True if installing the package, False if uninstalling.
"""
super().__init__()
self.package: Union[str, List[str]] = package
self.install = install
def _install_or_remove(self, pkg: str, logger: Logger, skip_repo_update: bool = False) -> bool:
"""
If `self.install` is True:
Call `install_dependency` and pass the package and logger to it.
If the result of `install_dependency` is False then set `self.error` equal to the message from the call.
return the True if `install_dependency` returned True, otherwise return False.
If `self.install` is False:
Call `uninstall_dependency` and pass the package and logger to it.
If the result of `uninstall_dependency` is False then set `self.error` equal to the message from the call.
return the True if `uninstall_dependency` returned True, otherwise return False
:param pkg: The name of the package to install/uninstall.
:param logger: An instance of a logger to provide insight.
:return: True if call there were no errors, otherwise False.
:return:
"""
if self.install:
success, msg = install_dependency(package=pkg, logger=logger, skip_repo_update=skip_repo_update)
if not success:
if not self.error:
self.error = msg
else:
self.error += f'{msg}\n'
return success
else:
success, msg = uninstall_dependency(package=pkg, logger=logger)
if not success:
if not self.error:
self.error = msg
else:
self.error += f'{msg}\n'
return success
def do_work(self, logger: Logger) -> bool:
"""
If `self.package` is a List:
Attempt to install each every package in the list. If a single package fails to install then this method
will return False.
:param logger: An instance of a logger to provide insight.
:return: True if call there were no errors, otherwise False.
"""
if isinstance(self.package, list):
update_repository(logger)
results = [self._install_or_remove(pkg, logger, True) for pkg in self.package]
return False not in results
elif isinstance(self.package, str):
return self._install_or_remove(self.package, logger)
else:
raise TypeError(f'Package is expected to be a list of strings or a single string. Got {type(self.package)} instead.')
def stop(self):
"""
Kill the opkg process if it is running.
:return:
"""
if not self.is_complete:
os.system('killall -9 opkg')

3
Libraries/Python3/pineapple/jobs/__init__.py

@ -0,0 +1,3 @@
from pineapple.jobs.job import Job
from pineapple.jobs.job_runner import JobRunner
from pineapple.jobs.job_manager import JobManager

42
Libraries/Python3/pineapple/jobs/job.py

@ -0,0 +1,42 @@
from typing import TypeVar, Generic, Optional
from logging import Logger
import abc
TResult = TypeVar('TResult')
class Job(Generic[TResult]):
def __init__(self):
self.is_complete: bool = False
self.result: Optional[TResult] = None
self.error: Optional[str] = None
@property
def was_successful(self) -> bool:
"""
Checks if the job complete without an error.
If the job has not completed or if it complete with no errors return True.
If the job completed with an error then return False.
:return: True if the job completed without an error, otherwise False
"""
return self.error is None and self.is_complete
@abc.abstractmethod
def do_work(self, logger: Logger) -> TResult:
"""
Override this method and implement a long running job.
This function should return whatever the result of the work is.
:param logger: An instance of a logger that may be used to provide insight.
:return: The result of the work.
"""
raise NotImplementedError()
@abc.abstractmethod
def stop(self):
"""
Override this method and implement a way to stop the running jub.
:return:
"""
raise NotImplementedError()

162
Libraries/Python3/pineapple/jobs/job_manager.py

@ -0,0 +1,162 @@
from typing import Dict, Optional, List, Callable, Tuple, Union
from uuid import uuid4
from pineapple.modules.module import Module
from pineapple.modules.request import Request
from pineapple.jobs.job import Job
from pineapple.jobs.job_runner import JobRunner
from pineapple.logger import *
class JobManager:
def __init__(self, name: str, log_level: int = logging.ERROR, module: Optional[Module] = None):
"""
:param name: The name of the job manager.
:param log_level: Optional level for logging. Default is ERROR
:param module: Optional instance of Module. If given some action and shutdown handlers will be registered.
Checkout `_setup_with_module` for more details.
"""
self.name = name
self.logger = get_logger(name, log_level)
self.jobs: Dict[str, JobRunner] = {}
self._setup_with_module(module)
def get_job(self, job_id: str, remove_if_complete: bool = True) -> Optional[Job]:
"""
Attempt to get a job by its id. If the job_id doesn't exist then None is returned.
If `remove_if_complete` is True the job will be deleted from memory only if it is completed.
This is the default behavior to prevent JobManager from tacking up unnecessary memory.
:param job_id: The id of the job to find.
:param remove_if_complete: True to delete the job from memory after its complete. (Default: True)
:return: an instance of Job if found, else None
"""
job_runner = self.jobs.get(job_id)
if not job_runner:
self.logger.debug(f'No job found matching id {job_id}.')
return None
job = job_runner.job
if remove_if_complete and job.is_complete:
self.logger.debug(f'Removing completed job: {job_id}.')
self.remove_job(job_id)
return job
def prune_completed_jobs(self):
"""
Removes all completed jobs from memory.
"""
self.logger.debug('Pruning jobs...')
running_jobs: Dict[str, JobRunner] = {}
current_jobs = len(self.jobs)
for job_id, job in self.jobs:
if job.is_complete:
self.remove_job(job_id)
self.logger.debug(f'Pruned {current_jobs - len(running_jobs)} jobs.')
def remove_job(self, job_id: str):
"""
Remove a job from memory based on its id.
This will remove the job regardless of its completion status.
:param job_id: The id of the job to delete.
:return:
"""
del self.jobs[job_id]
self.logger.debug(f'Removed job {job_id}.')
def execute_job(self, job: Job, callbacks: List[Callable[[Job], None]] = None) -> str:
"""
Assign an id to a job and execute it in a background thread.
The id will be returned and the job can be tracked by calling `get_job` and providing it the id.
:param job: an instance of Job to start running.
:param callbacks: An optional list of functions that take `job` as a parameter to be called when completed.
These will be called regardless if `job` raises an exception or not.
:return: The id of the running job.
"""
job_id = str(uuid4())
self.logger.debug(f'Assign job the id: {job_id}')
job_runner = JobRunner(job, self.logger, callbacks)
self.jobs[job_id] = job_runner
self.logger.debug('Starting job...')
job_runner.setDaemon(True)
job_runner.start()
self.logger.debug('Job started!')
return job_id
def stop_job(self, job: Optional[Job] = None, job_id: Optional[str] = None):
"""
Call the `stop` method on a job.
Either an instance of the Job to stop or id of the job is expected.
The job will not automatically be removed from memory on completion.
:param job: An instance of Job
:param job_id: The id of te job to stop
"""
if not job and not job_id:
raise Exception('A job or job_id is expected.')
if not job:
job = self.get_job(job_id, remove_if_complete=False)
if isinstance(job, Job):
job.stop()
def _setup_with_module(self, module: Optional[Module]):
"""
If module is not None and is an instance of Module then register the following action handlers:
action: `poll_job` | handler: `self.poll_job`
And register _on_module_shutdown as a shutdown handler.
:param module: an instance of Module
"""
if not module or not isinstance(module, Module):
return
module.register_action_handler('poll_job', self._poll_job)
module.register_shutdown_handler(self._on_module_shutdown)
def _on_module_shutdown(self, signal: int):
"""
A shutdown handler to be registered is `self.module` is not None.
This will stop all currently running jobs.
:param signal: The signal given
"""
for job_id, runner in self.jobs.items():
self.stop_job(job_id=job_id)
def _poll_job(self, request: Request) -> Union[dict, Tuple[str, bool]]:
"""
A module action handler to be used for checking the status of a background job.
The request object must contain string `job_id` which is used to lookup the running job.
Optionally, the request can contain boolean `remove_if_complete`. If this is True then the job will
be deleted from memory if it is completed. If this value is False then the job will remain until manually deleted.
This default value is True.
:param request: An instance of Request
"""
job_id = request.__dict__.get('job_id')
remove_if_complete = request.__dict__.get('remove_if_complete', True)
if not job_id:
return 'job_id was not found in request.', False
job = self.get_job(job_id, remove_if_complete)
if not job:
return 'No job found by that id.', False
return {'is_complete': job.is_complete, 'result': job.result, 'job_error': job.error}

53
Libraries/Python3/pineapple/jobs/job_runner.py

@ -0,0 +1,53 @@
from typing import Callable, List
from threading import Thread
from logging import Logger
from pineapple.jobs.job import Job
class JobRunner(Thread):
def __init__(self, job: Job, logger: Logger, callbacks: List[Callable[[Job], None]] = None):
"""
:param job: An instance of Job to run on a background thread.
:param logger: An instance of Logger to provide insight.
:param callbacks: An optional list of functions that take `job` as a parameter to be called when completed.
These will be called regardless if `job` raises an exception or not.
"""
super().__init__()
self.logger = logger
self.job: Job = job
self.running: bool = False
self._callbacks: List[Callable[[Job], None]] = callbacks if callbacks else list()
def run(self):
"""
Call the `do_work` method on `self.job` and assign the results to `self.job.result`.
If an exception is raised by the `do_work` method, catch it and set `self.job.error` equal to it.
After `do_work` finishes set `self.job.is_complete` equal to True.
"""
self.running = True
try:
self.job.result = self.job.do_work(self.logger)
except Exception as e:
self.logger.error(f'Running job encountered a {type(e)} error: {e}')
self.job.error = str(e)
self.job.is_complete = True
try:
if isinstance(self._callbacks, list) and len(self._callbacks) > 0:
for callback in self._callbacks:
callback(self.job)
except Exception as e:
self.logger.error(f'Callback failed with a {type(e)} error: {e}')
self.running = False
def stop(self):
"""
Call the `stop` method on `self.job` if the job is running.
:return:
"""
if self.running:
self.job.stop()

27
Libraries/Python3/pineapple/logger/__init__.py

@ -0,0 +1,27 @@
from pineapple.logger.pretty_formatter import PrettyFormatter
from logging.handlers import RotatingFileHandler
from logging import Logger
import logging
def get_logger(name: str, level: int, log_to_file: bool = True, console_logger_level: int = logging.DEBUG) -> Logger:
logger = logging.getLogger(name)
logger.setLevel(level)
if logger.hasHandlers():
logger.handlers.clear()
if log_to_file:
log_format = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)")
file_handler = RotatingFileHandler(f'/tmp/modules/{name}.log', maxBytes=1024*1024)
file_handler.setFormatter(log_format)
file_handler.setLevel(level)
logger.addHandler(file_handler)
if level <= console_logger_level:
console_logger = logging.StreamHandler()
console_logger.setFormatter(PrettyFormatter())
logger.addHandler(console_logger)
return logger

25
Libraries/Python3/pineapple/logger/pretty_formatter.py

@ -0,0 +1,25 @@
import logging
class PrettyFormatter(logging.Formatter):
grey = "\x1b[38;21m"
yellow = "\x1b[33;21m"
red = "\x1b[31;21m"
bold_red = "\x1b[31;1m"
reset = "\x1b[0m"
light_blue = "\x1b[1;34m"
format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)"
FORMATS = {
logging.DEBUG: grey + format + reset,
logging.INFO: light_blue + format + reset,
logging.WARNING: yellow + format + reset,
logging.ERROR: red + format + reset,
logging.CRITICAL: bold_red + format + reset
}
def format(self, record):
log_fmt = self.FORMATS.get(record.levelno)
formatter = logging.Formatter(log_fmt)
return formatter.format(record)

2
Libraries/Python3/pineapple/modules/__init__.py

@ -0,0 +1,2 @@
from pineapple.modules.module import Module
from pineapple.modules.request import Request

364
Libraries/Python3/pineapple/modules/module.py

@ -0,0 +1,364 @@
import os
import socket
import json
import logging
import signal
from typing import Tuple, Any, Callable, Optional, Dict, Union, List
from pineapple.logger import get_logger
from pineapple.modules.request import Request
from pineapple.helpers import json_to_bytes
import pineapple.helpers.notification_helpers as notifier
class Module:
def __init__(self, name: str, log_level: int = logging.WARNING):
"""
:param name: The name of the module. Example `cabinet`
:param log_level: The level of logging you wish to show. Default WARNING
"""
self.logger = get_logger(name, log_level) # logger for feedback.
self.name = name # the name of the module
self.logger.debug(f'Initializing module {name}.')
# A list of functions to called when module is started.
self._startup_handlers: List[Callable[[], None]] = []
# A list of functions to be called when module is stopped.
self._shutdown_handlers: List[Callable[[int], None]] = []
# A dictionary mapping an action to a function.
self._action_handlers: Dict[str, Callable[[Request], Union[Any, Tuple[bool, Any]]]] = {}
self._running: bool = False # set to False to stop the module loop
# api requests will be received over this socket
self._module_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self._module_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self._module_socket_path = f'/tmp/modules/{name}.sock' # apth to the socket
self._buffer_size = 10485760
# if the socket already exists attempt to delete it.
try:
os.unlink(self._module_socket_path)
except OSError:
if os.path.exists(self._module_socket_path):
self.logger.error('Could not remove existing socket!')
raise FileExistsError('Could not remove existing socket!')
# If a SIGINT is received preform a clean shutdown by calling `shutdown()`
signal.signal(signal.SIGINT, self.shutdown)
signal.signal(signal.SIGTERM, self.shutdown)
def _receive(self) -> Optional[dict]:
"""
Receive data over a socket and attempt to json deserialize it.
If the deserialization fails, None will be returned
:return: A dictionary containing the data received over the socket or None if json deserialization fails.
"""
connection, _ = self._module_socket.accept()
data = connection.recv(self._buffer_size)
decoded_data = data.decode('utf-8')
try:
return json.loads(decoded_data)
except ValueError:
self.logger.warning('Non-JSON Received')
return None
def _publish(self, message: bytes):
"""
Publish a message `message` to over `_module_socket`.
Call this method to respond to a request.
:param message: Bytes of a message that should be sent
:return: None
"""
self.logger.debug('Accepting on module socket')
connection, _ = self._module_socket.accept()
try:
self.logger.debug(f'Sending response {str(message, "utf-8")}')
connection.sendall(message)
except ValueError:
self.logger.error('Could not send response!')
def _handle_request(self, request: Request):
"""
Attempt to find an handle for the requests actions and call it.
If there is no action registered for the request `request`, an error will be sent back over `module_socket`.
If there is a handler registered the following will happen:
* the action handler will be called
* if the action handler returns an error, an error will be sent back over `module_socket`
* if the action handler returns success, the data will be sent back over `module_socket`
:param request: The request instance to handle
:return: None
"""
handler: Callable[[Request], Union[Any, Tuple[Any, bool]]] = self._action_handlers.get(request.action)
if not handler:
self._publish(json_to_bytes({'error': f'No action handler registered for action {request.action}'}))
self.logger.error(f'No action handler registered for action {request.action}')
return
try:
self.logger.debug(f'Calling handler for action {request.action} and passing {request.__dict__}')
result = handler(request)
except Exception as e:
self.logger.error(f'Handler raised exception: {e}')
self._publish(json_to_bytes({'error': f'Handler raised exception: {e}'}))
return
if isinstance(result, tuple):
if len(result) > 2:
self.logger.error(f'Action handler `{request.action}` returned to many values.')
self._publish(json_to_bytes({'error': f'Action handler `{request.action}` returned to many values.'}))
return
if not isinstance(result[1], bool):
self.logger.error(f'{request.action}: second value expected to be a bool but got {type(result[1])} instead.')
self._publish(json_to_bytes({
'error': f'{request.action}: second value expected to be a bool but got {type(result[1])} instead.'
}))
return
data, success = result
else:
success = True
data = result
if success:
response_dict = {'payload': data}
else:
response_dict = {'error': data}
message_bytes = json_to_bytes(response_dict)
# if the message is to big to be sent over the socket - return an error instead.
if len(message_bytes) > self._buffer_size:
self.logger.error(f'Response of {len(message_bytes)} bytes exceeds limit of {self._buffer_size}')
message_bytes = json_to_bytes({
'error': 'Response of {len(message_bytes)} bytes exceeds limit of {self._buffer_size}'
})
self._publish(message_bytes)
def shutdown(self, sig=None, frame=None):
"""
Attempt to clean shutdown the module.
If your module has anything it needs to close or otherwise cleanup upon shutdown, please override this
and do what you need to here. Be sure you call `super.shutdown()` in your new implementation.
This method may also be called to handle signals such as SIGINT. If it was called as a signal handler the
signal `sig` and frame `frame` will be passed into this method.
:param sig: Optional signal that triggered a signal handler
:param frame: Optional frame
:return: None
"""
self.logger.debug(f'Calling {len(self._shutdown_handlers)} shutdown handlers.')
try:
for handler in self._shutdown_handlers:
handler(sig)
except Exception as e:
self.logger.warning(f'Shutdown handler raised an exception: {str(e)}')
try:
os.unlink(f'/tmp/modules/{self.name}.sock')
os.unlink(f'/tmp/modules/{self.name}.pid')
except Exception as e:
self.logger.warning(f'Error deleting socket or pid file: {str(e)}')
self.logger.info(f'Shutting down module. Signal: {sig}')
self._running = False
self._module_socket.close()
def start(self):
"""
Main loop for the module which will run as long as `_running` is True.
This will listen for data coming over `_module_socket` and deserialize it to a `Request` object.
That object is then passed to `handle_request` for further processing.
If an exception is thrown, this loop will stop working and attempt to do a clean shutdown of the module by
calling `shutdown`.
:return: None
"""
self.logger.info('Starting module...')
self.logger.debug(f'Binding to socket {self._module_socket_path}')
self._module_socket.bind(self._module_socket_path)
self._module_socket.listen(1)
self.logger.debug('Listening on socket!')
self.logger.debug(f'Calling {len(self._startup_handlers)} startup handlers.')
for handler in self._startup_handlers:
try:
handler()
except Exception as e:
self.logger.warning(f'Startup handler raised an exception: {str(e)}')
self._running = True
while self._running:
try:
request_dict: Optional[dict] = self._receive()
if not request_dict:
self.logger.debug("Received non-json data over the socket.")
continue
self.logger.debug('Processing request.')
request = Request()
request.__dict__ = request_dict
self._handle_request(request)
except OSError as os_error:
self.logger.warning(f'An os error occurred: {os_error}')
except Exception as e:
self.logger.critical(f'A fatal `{type(e)}` exception was thrown: {e}')
self.shutdown()
def register_action_handler(self, action: str, handler: Callable[[Request], Union[Any, Tuple[Any, bool]]]):
"""
Manually register an function `handler` to handle an action `action`.
This function will be called anytime a request with the matching action is received.
The action handler must take a positional argument of type `Request`. This must be the first argument.
Usage Example:
module = Module('example')
def save_file(request: Request) -> Union[Any, Tuple[Any, bool]]:
...
module.register_action_handler(save_file)
:param action: The request action to handle
:param handler: A function that takes `Request` that gets called when the matching `action` is received.
"""
self._action_handlers[action] = handler
def handles_action(self, action: str):
"""
A decorator that registers a function as an handler for a given action `action` in a request.
The decorated function is expected take an instance of `Request` as its first argument and can return either
Any or a tuple with two values - Any, bool - in that order.
If the function does not return a tuple, The response is assumed to be successful and the returned value
will be json serialized and placed into the 'payload' of the response body.
Example Function:
@handles_action('save_file')
def save_file(request: Request) -> str:
...
return 'Filed saved successfully!'
Example Response:
{ "payload": "File saved successfully!" }
If a tuple is returned, the first value in the tuple will the data sent back to the user. The second value
must be a boolean that indicates whether the function was successful (True) or not (False). If this
value is True, the data in the first index will be sent back in the response payload.
Example Function:
@handles_action('save_file')
def save_file(request: Request) -> Tuple[str, bool]:
...
return 'Filed saved successfully!', True
Example Response:
{ "payload": "File saved successfully!" }
However, if this value is False, The data in the first index will be sent back as an error.
Example Function:
@handles_action('save_file')
def save_file(request: Request) -> Tuple[str, bool]:
...
return 'There was an issue saving the file.', False
Example Response:
{ "error": There was an issue saving the file." }
:param action: The request action to handle
"""
def wrapper(func: Callable[[Request], Union[Any, Tuple[Any, bool]]]):
self.register_action_handler(action, func)
return func
return wrapper
def register_shutdown_handler(self, handler: Callable[[Optional[int]], None]):
"""
Manually register a function `handler` to be called on the module shutdown lifecycle event.
This handler function must take an integer as a parameter which may be the kill signal sent to the application.
Depending on how the module is shutdown, the signal value may be None.
Example:
module = Module('example')
def stop_all_tasks(signal: int):
...
module.register_shutdown_handler(stop_all_tasks)
:param handler: A function to be called on shutdown lifecycle event.
"""
self._shutdown_handlers.append(handler)
def on_shutdown(self):
"""
A decorator that registers a function as a shutdown handler to be called on the shutdown lifecycle event.
In the example below, the function `stop_all_tasks` will be called when the module process is terminated.
Example:
@module.on_shutdown()
def stop_all_tasks(signal: int):
...
"""
def wrapper(func: Callable[[int], None]):
self.register_shutdown_handler(func)
return func
return wrapper
def register_startup_handler(self, handler: Callable[[], None]):
"""
Manually register a function `handler` to be called on the module start lifecycle event.
This handler function most not take any arguments.
Example:
module = Module('example')
def copy_configs():
...
module.register_startup_handler(copy_configs)
:param handler:
:return:
"""
self._startup_handlers.append(handler)
def on_start(self):
"""
A decorator that registers a function as a startup handler to be called on the start lifecycle event.
In the example below, the function `copy_configs` will be called when the modules `start` method is called.
Example:
@module.on_start()
def copy_configs():
...
:return:
"""
def wrapper(func: Callable[[], None]):
self.register_startup_handler(func)
return func
return wrapper
def send_notification(self, message: str, level: int) -> bool:
"""
Send a notification over the WiFi Pineapples notification socket
:param message: Notification message
:param level: Notification level
:return: bool
"""
return notifier.send_notification(message, self.name, level)

9
Libraries/Python3/pineapple/modules/request.py

@ -0,0 +1,9 @@
import json
class Request:
def __init__(self):
self.module: str = ""
self.action: str = ""
def __repr__(self):
return json.dumps(self.__dict__)

6
Libraries/README.md

@ -0,0 +1,6 @@
# WiFi Pineapple Mark 7 Module Libraries
This section of the GitHub repository holds the code for the Python module library that exposes helpers and frameworks to enable you to develop modules much easier.
More language support is planned in the future, and contributions are welcome.

11
README.md

@ -0,0 +1,11 @@
# WiFi Pineapple Mark 7 Modules
This repository contains modules for the WiFi Pineapple Mark 7. All the community developed modules are here, and developers should create pull requests for any changes to modules, and to submit new modules.
## Documentation
* [WiFi Pineapple Mark 7 Modules Guide](https://docs.hak5.org/hc/en-us/articles/360052162434)
* [WiFi Pineapple Mark 7 REST API](https://docs.hak5.org/hc/en-us/articles/360049854174-WiFi-Pineapple-Mark-VII-REST-API)
* [WiFi Pineapple Mark 7 TypeScript API](https://docs.hak5.org/hc/en-us/articles/360058059233)
* [Contributing to the WiFi Pineapple Mark 7 Modules Repository](https://docs.hak5.org/hc/en-us/articles/360056213714)

13
cabinet/.editorconfig

@ -0,0 +1,13 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

46
cabinet/.gitignore

@ -0,0 +1,46 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out
# dependencies
/node_modules
# profiling files
chrome-profiler-events*.json
speed-measure-plugin*.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db

47
cabinet/angular.json

@ -0,0 +1,47 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"cabinet": {
"projectType": "library",
"root": "projects/cabinet",
"sourceRoot": "projects/cabinet/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-ng-packagr:build",
"options": {
"tsConfig": "projects/cabinet/tsconfig.lib.json",
"project": "projects/cabinet/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "projects/cabinet/tsconfig.lib.prod.json"
}
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "projects/cabinet/src/test.ts",
"tsConfig": "projects/cabinet/tsconfig.spec.json",
"karmaConfig": "projects/cabinet/karma.conf.js"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"projects/cabinet/tsconfig.lib.json",
"projects/cabinet/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
}},
"defaultProject": "cabinet"
}

42
cabinet/build.sh

@ -0,0 +1,42 @@
#!/bin/bash
# Step 1: Build the Angular module
ng build --prod > /dev/null 2>&1
RET=$?
if [[ $RET -ne 0 ]]; then
echo "[!] Angular Build Failed: Run 'ng build --prod' to figure out why."
exit 1
else
echo "[*] Angular Build Succeeded"
fi
# Step 2: Copy the required files to the build output
MODULENAME=$(basename $PWD)
cp -r projects/$MODULENAME/src/module.svg dist/$MODULENAME/bundles/
cp -r projects/$MODULENAME/src/module.json dist/$MODULENAME/bundles/
cp -r projects/$MODULENAME/src/module.py dist/$MODULENAME/bundles/ > /dev/null 2>&1
cp -r projects/$MODULENAME/src/module.php dist/$MODULENAME/bundles/ > /dev/null 2>&1
cp -r projects/$MODULENAME/src/assets/ dist/$MODULENAME/bundles/ > /dev/null 2>&1
# Step 3: Clean up
rm -rf dist/$MODULENAME/bundles/*.map
rm -rf dist/$MODULENAME/bundles/*.min*
rm -rf bundletmp
mv dist/$MODULENAME/bundles/ bundletmp
rm -rf dist/$MODULENAME/*
mv bundletmp/* dist/$MODULENAME/
rm -rf bundletmp
# Step 4: Package (Optional)
if [[ $1 == "package" ]]; then
VERS=$(cat dist/$MODULENAME/module.json | grep "version" | awk '{split($0, a, ": "); gsub("\"", "", a[2]); gsub(",", "", a[2]); print a[2]}')
rm -rf $MODULENAME-$VERS.tar.gz
echo "[*] Packaging $MODULENAME (Version $VERS)"
cd dist/
tar -pczf $MODULENAME-$VERS.tar.gz $MODULENAME
mv $MODULENAME-$VERS.tar.gz ../
cd ../
else
echo "[*] Skipping Packaging (Run ./build.sh package to generate)"
fi

14727
cabinet/package-lock.json
File diff suppressed because it is too large
View File

52
cabinet/package.json

@ -0,0 +1,52 @@
{
"name": "cabinet",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "~9.1.11",
"@angular/cdk": "^9.2.4",
"@angular/common": "~9.1.11",
"@angular/compiler": "~9.1.11",
"@angular/core": "~9.1.11",
"@angular/flex-layout": "^9.0.0-beta.31",
"@angular/forms": "~9.1.11",
"@angular/material": "^9.2.4",
"@angular/platform-browser": "~9.1.11",
"@angular/platform-browser-dynamic": "~9.1.11",
"@angular/router": "~9.1.11",
"rxjs": "~6.5.5",
"tslib": "^1.10.0",
"zone.js": "~0.10.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.901.8",
"@angular-devkit/build-ng-packagr": "~0.901.8",
"@angular/cli": "~9.1.8",
"@angular/compiler-cli": "~9.1.11",
"@angular/language-service": "~9.1.11",
"@types/jasmine": "~3.5.10",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^12.11.1",
"codelyzer": "^5.1.2",
"jasmine-core": "~3.5.0",
"jasmine-spec-reporter": "~5.0.2",
"karma": "~5.1.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~3.0.3",
"karma-jasmine": "~3.3.1",
"karma-jasmine-html-reporter": "^1.4.0",
"ng-packagr": "^9.0.0",
"protractor": "~7.0.0",
"ts-node": "~8.10.2",
"tslint": "~6.1.2",
"typescript": "^3.6.5"
}
}

7
cabinet/projects/cabinet/ng-package.json

@ -0,0 +1,7 @@
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/cabinet",
"lib": {
"entryFile": "src/public-api.ts"
}
}

11
cabinet/projects/cabinet/package.json

@ -0,0 +1,11 @@
{
"name": "cabinet",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^8.2.14",
"@angular/core": "^8.2.14"
},
"scripts": {
"build": "ng build --prod"
}
}

43
cabinet/projects/cabinet/src/lib/cabinet.module.ts

@ -0,0 +1,43 @@
import { NgModule } from '@angular/core';
import {CommonModule} from '@angular/common';
import { cabinetComponent } from './components/cabinet.component';
import { RouterModule, Routes } from '@angular/router';
import {FlexLayoutModule} from '@angular/flex-layout';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {MaterialModule} from './modules/material/material.module';
import {CabinetDeleteDialogComponent} from './components/helpers/delete-dialog/cabinet-delete-dialog.component';
import {CabinetErrorDialogComponent} from './components/helpers/error-dialog/cabinet-error-dialog.component';
import {FileEditorDialogComponent} from './components/helpers/file-editor-dialog/cabinet-file-editor-dialog.component';
import {NewFolderDialogComponent} from './components/helpers/new-folder-dialog/cabinet-new-folder-dialog.component';
const routes: Routes = [
{ path: '', component: cabinetComponent }
];
@NgModule({
declarations: [
cabinetComponent,
CabinetDeleteDialogComponent,
NewFolderDialogComponent,
FileEditorDialogComponent,
CabinetErrorDialogComponent
],
imports: [
RouterModule.forChild(routes),
MaterialModule,
CommonModule,
FormsModule,
FlexLayoutModule,
ReactiveFormsModule
],
exports: [cabinetComponent],
entryComponents: [
CabinetDeleteDialogComponent,
NewFolderDialogComponent,
FileEditorDialogComponent,
CabinetErrorDialogComponent
],
})
export class cabinetModule { }

25
cabinet/projects/cabinet/src/lib/components/cabinet.component.css

@ -0,0 +1,25 @@
.cabinet-control-container {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-self: flex-start;
}
.control-button {
margin-top: 16px;
margin-right: 4px;
}
.action-button {
margin-top: 4px;
margin-right: 4px;
margin-bottom: 4px;
}
.cabinet-loading-centered {
display: flex;
justify-content: center;
align-self: center;
margin-top: 16px;
margin-bottom: 16px;
}

88
cabinet/projects/cabinet/src/lib/components/cabinet.component.html

@ -0,0 +1,88 @@
<mat-card>
<mat-card-content>
<div class="cabinet-control-container">
<div>
<mat-card-title>Cabinet</mat-card-title>
<mat-card-subtitle>Current Directory <i>{{ currentDirectory }}</i></mat-card-subtitle>
</div>
<span fxFlex></span>
<div class="cabinet-control-container">
<mat-spinner [diameter]="24" color="accent" class="control-button" *ngIf="isBusy"></mat-spinner>
<button mat-raised-button
color="accent"
class="control-button"
(click)="getDirectoryContents(currentDirectory, true);"
[disabled]="currentDirectory == '/' || isBusy">Back
</button>
<button mat-raised-button
color="accent"
class="control-button"
(click)="showCreateDirectory();"
[disabled]="isBusy">New Folder
</button>
<button mat-raised-button
color="accent"
class="control-button"
(click)="showEditDialog(null);"
[disabled]="isBusy">New File</button>
<button mat-raised-button
color="accent"
class="control-button"
(click)="getDirectoryContents(currentDirectory);"
[disabled]="isBusy">Refresh
</button>
</div>
</div>
<mat-divider></mat-divider>
<div class="cabinet-loading-centered" *ngIf="isBusy && directoryContents.length == 0">
<i>Loading...</i>
<mat-spinner [diameter]="18" color="accent" style="margin-left: 8px"></mat-spinner>
</div>
<div class="cabinet-loading-centered" *ngIf="!isBusy && directoryContents.length == 0">
<span>
<p>Directory <i>{{ currentDirectory }}</i> appears to be empty</p>
<button mat-flat-button
color="accent"
style="width: 100%"
(click)="getDirectoryContents(currentDirectory, true);">Back</button>
</span>
</div>
<mat-table style="display: none">
<mat-header-row *matHeaderRowDef="[]"></mat-header-row>
</mat-table>
<table class="mat-table" style="min-width: 100%; overflow-x: auto; justify-content: left" *ngIf="directoryContents.length > 0">
<thead>
<tr class="mat-header-row">
<th class="mat-header-cell">File Name</th>
<th class="mat-header-cell">Location</th>
<th class="mat-header-cell">Permissions</th>
<th class="mat-header-cell">Size</th>
<th class="mat-header-cell">Actions</th>
</tr>
</thead>
<tbody>
<ng-container *ngFor="let item of directoryContents">
<tr class="mat-row">
<td class="mat-cell" *ngIf="!item.is_directory">{{ item.name }}</td>
<td class="mat-cell" *ngIf="item.is_directory">
<button mat-button color="accent" (click)="getDirectoryContents(item.path);">
{{ item.name }}
</button>
</td>
<td class="mat-cell">{{ item.path }}</td>
<td class="mat-cell">{{ item.permissions }}</td>
<td class="mat-cell">{{ item.size }}</td>
<td class="mat-cell">
<button mat-flat-button color="warn" class="action-button" (click)="showDeleteConfirmation(item);">Delete</button>
<button mat-flat-button color="accent" class="action-button" *ngIf="!item.is_directory" (click)="showEditDialog(item)">Edit</button>
</td>
</tr>
</ng-container>
</tbody>
</table>
</mat-card-content>
</mat-card>

176
cabinet/projects/cabinet/src/lib/components/cabinet.component.ts

@ -0,0 +1,176 @@
import { Component, OnInit } from '@angular/core';
import {MatDialog } from '@angular/material/dialog';
import {ApiService} from '../services/api.service';
import {CabinetDeleteDialogComponent} from './helpers/delete-dialog/cabinet-delete-dialog.component';
import {NewFolderDialogComponent} from './helpers/new-folder-dialog/cabinet-new-folder-dialog.component';
import {FileEditorDialogComponent} from './helpers/file-editor-dialog/cabinet-file-editor-dialog.component';
import {CabinetErrorDialogComponent} from './helpers/error-dialog/cabinet-error-dialog.component';
@Component({
selector: 'lib-cabinet',
templateUrl: 'cabinet.component.html',
styleUrls: ['cabinet.component.css'],
})
export class cabinetComponent implements OnInit {
public isBusy: boolean = false;
public currentDirectory: string = '/';
public directoryContents: Array<object> = [];
constructor(private API: ApiService,