Compare commits
No commits in common. "master" and "php-legacy-version" have entirely different histories.
master
...
php-legacy
|
@ -1,143 +0,0 @@
|
|||
*.db
|
||||
*.pyc
|
||||
app.pid
|
||||
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
After Width: | Height: | Size: 386 KiB |
After Width: | Height: | Size: 210 KiB |
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 2.0 MiB |
After Width: | Height: | Size: 1.8 MiB |
After Width: | Height: | Size: 2.1 MiB |
After Width: | Height: | Size: 2.0 MiB |
After Width: | Height: | Size: 2.4 MiB |
After Width: | Height: | Size: 1019 KiB |
34
README.md
|
@ -2,17 +2,22 @@
|
|||
WebFM is a media and file viewer aspiring to become a full fledged file manager in the browser.
|
||||
|
||||
# Usage
|
||||
1. Install python, sqlite3, and ffmpeg on the system this will be on.
|
||||
3. Use ufw or gufw to open the port on your computer to the local network.
|
||||
4. Use hosts file (or other methods) to redirect webfm.com and ssoapps.com to local app.
|
||||
5. Update client_secrets.json > 'client_secret' field with your Keycloak key. (Current one was local, not public, and has been expired)
|
||||
6. Place files or start uploading some to the folders.
|
||||
7. Place an image such as a jpg, png, or gif labeled "000.itsExtension" in a directory and the viewer will use it as the background image for that folder/directory.
|
||||
7. Password protect folder based on core/utils/shellfm/windows/Settings.py file settings.
|
||||
8. Save paths to favorites list for quick access.
|
||||
1. Install php7, php-sqlite3, and ffmpeg on the system this will be on.
|
||||
2. Use php -S 0.0.0.0:yourDesiredPort
|
||||
3. Use ufw or gufw to open the port on your computer to the network.
|
||||
4. Place files or start uploading some to the folders.
|
||||
5. Double click thumbnails and container outlines to open files.
|
||||
6. Double click the text name to change the file's or folder's name and press enter to set it.
|
||||
7. Right-click to get context menu options.
|
||||
8. Place an image such as a jpg, png, or gif labeled "000.itsExtension" in a directory then the viewer will use it as the background image for that folder/directory.
|
||||
9. Password protect folder based on resources/php/config.php file setting.
|
||||
10. Save paths to favorites list for quick access.
|
||||
|
||||
Notes:
|
||||
n/a
|
||||
1. The provided folders except "resources" are optional. You can add and remove them as you please.
|
||||
2. The media and image pane can be moved by dragging from the transparentish bar that has the close button and other controls.
|
||||
3. Edit the resources/php/config.php file and put your own programs there.
|
||||
4. Edit your php.ini file "upload_max_filesize" and "post_max_size" to be higher to upload larger files.
|
||||
|
||||
# TO-DO
|
||||
1. Allow for move and copy.
|
||||
|
@ -20,8 +25,9 @@ n/a
|
|||
|
||||
|
||||
# Images
|
||||
![1 Videos List](images/pic1.png)
|
||||
![2 Video Playing](images/pic2.png)
|
||||
![3 Images List](images/pic3.png)
|
||||
![4 Context menu](images/pic4.png)
|
||||
![5 Settings Pane With Upload And Create Functionality](images/pic5.png)
|
||||
![1 Home](Images/pic1.png)
|
||||
![2 Images Listed](Images/pic2.png)
|
||||
![3 Videos Listed](Images/pic3.png)
|
||||
![4 Image Open](Images/pic4.png)
|
||||
![5 Image Open And Video Playing](Images/pic5.png)
|
||||
![6 Alternate Background](Images/pic6.png)
|
||||
|
|
After Width: | Height: | Size: 1.9 MiB |
|
@ -1,40 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
. CONFIG.sh
|
||||
|
||||
# set -o xtrace ## To debug scripts
|
||||
# set -o errexit ## To exit on error
|
||||
# set -o errunset ## To exit if a variable is referenced but not set
|
||||
|
||||
|
||||
function main() {
|
||||
rm -rf venv/
|
||||
|
||||
clear
|
||||
python -m venv venv/
|
||||
sleep 2
|
||||
source "./venv/bin/activate"
|
||||
|
||||
ANSR="-1"
|
||||
while [[ $ANSR != "0" ]] && [[ $ANSR != "1" ]] && [[ $ANSR != "2" ]]; do
|
||||
clear
|
||||
menu_mesage
|
||||
read -p "--> : " ANSR
|
||||
done
|
||||
case $ANSR in
|
||||
"1" ) pip install -r linux-requirements.txt;;
|
||||
"2" ) pip install -r windows-requirements.txt;;
|
||||
"0" ) exit;;
|
||||
* ) echo "Don't know how you got here but that's a bad sign...";;
|
||||
esac
|
||||
}
|
||||
|
||||
function menu_mesage() {
|
||||
echo "NOTE: Make sure to have Python 3 installed!"
|
||||
echo -e "\nWhat do you want to do?"
|
||||
echo -e "\t1) Generate Linux/Mac supported venv. (Installs Repuirements)"
|
||||
echo -e "\t2) Generate Windows supported venv. (Installs Repuirements)"
|
||||
echo -e "\t0) EXIT"
|
||||
}
|
||||
|
||||
main $@;
|
|
@ -0,0 +1 @@
|
|||
LOL...Not really!
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
BIN
images/pic1.png
Before Width: | Height: | Size: 1.3 MiB |
BIN
images/pic2.png
Before Width: | Height: | Size: 1.3 MiB |
BIN
images/pic3.png
Before Width: | Height: | Size: 1.0 MiB |
BIN
images/pic4.png
Before Width: | Height: | Size: 370 KiB |
BIN
images/pic5.png
Before Width: | Height: | Size: 299 KiB |
|
@ -0,0 +1,120 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="" charset="utf-8"/>
|
||||
<title>Web File Manager</title>
|
||||
<link type="text/css" rel="stylesheet" href="resources/css/base.css"/>
|
||||
<link type="text/css" rel="stylesheet" href="resources/css/main.css"/>
|
||||
<link rel="shortcut icon" type="image/png" href="favicon.png"/>
|
||||
</head>
|
||||
<body onload="onloadSetBG(); getDir('/')" contextmenu="menu">
|
||||
<!-- Background -->
|
||||
<img id="bg" />
|
||||
|
||||
<video id="video" src="" controls >
|
||||
</video>
|
||||
|
||||
<!-- Controls -->
|
||||
<!-- Create the menu -->
|
||||
<menu type="context" id="menu">
|
||||
<menuitem label="Home Directory" onclick="clearDirCookie()"></menuitem>
|
||||
<menuitem label="Show Server Messages" onclick="tgglElmView('serverMsgView')"></menuitem>
|
||||
<menuitem label="Clear Upload List" onclick="clearDlList()"></menuitem>
|
||||
<menuitem label="Download" onclick="downloadItem()"></menuitem>
|
||||
<menuitem label="Delete" onclick="deleteItem()"></menuitem>
|
||||
</menu>
|
||||
|
||||
<!-- Uploader -->
|
||||
<h2 id="controls">
|
||||
<button type="button" title="Other Options" onclick="tgglElmView('popOutControls')">⚙</button>
|
||||
<button type="button" title="Refresh" onclick="getDir('./')">↻</button>
|
||||
<button type="button" title="Back" onclick="getDir('../')">⇐</button>
|
||||
<input type="text" placeholder="Search..." onkeyup="searchPage(this)" name="" value="">
|
||||
<button type="button" onclick="clearSearch()" title="Clears search..." >Clear Search</button>
|
||||
<button onclick="getFavesList(); tgglElmView('favesList')">Faves List ↕</button>
|
||||
<button type="button" onclick="lockFolders()" title="Lock unlocked folders..." >Lock Unlocked Folders</button>
|
||||
|
||||
<br/>
|
||||
|
||||
<button type="button" id="faves" onclick="faveManager(this)" title="Add/Delete from favorites..." >☆</button>
|
||||
Path:<span id="path"></span>
|
||||
</h2>
|
||||
|
||||
<div id="popOutControls" style="display:none;">
|
||||
<center>
|
||||
<form>
|
||||
<input class="ulFile" type="file" title="files To Upload" name="filesToUpload[]" data-multiple-caption="{count} files selected" multiple />
|
||||
<input type="button" onclick="uploadFiles()" name="UploadFiles" title="Upload File(s)" value="Upload File(s)" />
|
||||
<input type="reset" title="Clear" id="CLEARBTTN" value="Clear" style="display:none;">
|
||||
<input type="text" id="DIRPATHUL" name="DIRPATHUL" value="">
|
||||
</form>
|
||||
<br/>
|
||||
<input type="text" id="NewItem" value=""/>
|
||||
<input type="button" value="New Dir" onclick="createItem('dir')"/>
|
||||
<input type="button" value="New File" onclick="createItem('file')"/>
|
||||
<input type="button" value="Show Server Messages" onclick="tgglElmView('serverMsgView')"/>
|
||||
<br/>
|
||||
<input id="MergeType" type="checkbox" onchange="getDir('./')" />
|
||||
<label for="MergeType">Show seassons in same list.</label>
|
||||
</center>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Dynamic content targets -->
|
||||
<ul id="favesList" style="display: none;"> </ul>
|
||||
<ul id="dynUl"></ul>
|
||||
|
||||
<!-- Uploader processor -->
|
||||
<div id="serverMsgView" style="display:none;"> </div>
|
||||
|
||||
|
||||
<!-- Templates -->
|
||||
<template id="dirTemplate">
|
||||
<li class="dirStyle" tabindex="1">
|
||||
<img id="dirID" class="systemIcon" src="" />
|
||||
<input id="titleID" class="dirTitle" type="text" value="" readonly="true" />
|
||||
</li>
|
||||
</template>
|
||||
<template id="vidTemplate">
|
||||
<li id="movieID" class="movieStyle" tabindex="1" style="background-image: url('')">
|
||||
<span class="popOutBttnInner" title="Open In Local Program">∽</span>
|
||||
<input id="titleID" class="movieTitle" type="text" value="" readonly="true" />
|
||||
</li>
|
||||
</template>
|
||||
<template id="imgTemplate">
|
||||
<img id="imageID" class="iconImg" src="" alt="">
|
||||
</template>
|
||||
<template id="filTemplate">
|
||||
<li class="fileStyle">
|
||||
<img id="fileID" class="systemIcon" src="" />
|
||||
<input id="titleID" class="fileTitle" type="text" value="" readonly="true" />
|
||||
</li>
|
||||
</template>
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
if (window.self !== window.top) {
|
||||
setTimeout(function () {
|
||||
let elm = document.getElementById("bg");
|
||||
elm.parentElement.removeChild(elm);
|
||||
|
||||
// Stylesheet for iframe views
|
||||
var link = document.createElement("link");
|
||||
link.href = "resources/css/iframe.css";
|
||||
link.type = "text/css";
|
||||
link.rel = "stylesheet";
|
||||
document.getElementsByTagName("head")[0].appendChild(link);
|
||||
}, 500);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="text/javascript" src="resources/js/favorites.js" charset="utf-8"></script>
|
||||
<script type="text/javascript" src="resources/js/passwordFieldInsert.js" charset="utf-8"></script>
|
||||
<script type="text/javascript" src="resources/js/cookieHandler.js" charset="utf-8"></script>
|
||||
<script type="text/javascript" src="resources/js/jsonParser.js" charset="utf-8"></script>
|
||||
<script type="text/javascript" src="resources/js/ajax.js" charset="utf-8"></script>
|
||||
<script type="text/javascript" src="resources/js/uiActions.js" charset="utf-8"></script>
|
||||
<script type="text/javascript" src="resources/js/filesystemActions.js" charset="utf-8"></script>
|
||||
<script type="text/javascript" src="resources/js/uiEvents.js" charset="utf-8"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,36 +0,0 @@
|
|||
bcrypt==4.0.1
|
||||
certifi==2022.12.7
|
||||
charset-normalizer==3.0.1
|
||||
click==7.1.2
|
||||
dnspython==1.16.0
|
||||
email-validator==1.1.2
|
||||
eventlet==0.30.1
|
||||
Flask==1.1.2
|
||||
Flask-Bcrypt==0.7.1
|
||||
Flask-Login==0.5.0
|
||||
flask-oidc==1.4.0
|
||||
Flask-SQLAlchemy==2.4.4
|
||||
Flask-Uploads==0.2.1
|
||||
Flask-WTF==0.14.3
|
||||
greenlet==1.0.0
|
||||
gunicorn==20.0.4
|
||||
httplib2==0.19.0
|
||||
idna==3.4
|
||||
itsdangerous==1.1.0
|
||||
Jinja2==2.11.3
|
||||
MarkupSafe==1.1.1
|
||||
oauth2client==4.1.3
|
||||
Pillow==9.4.0
|
||||
pyasn1==0.4.8
|
||||
pyasn1-modules==0.2.8
|
||||
pycairo==1.23.0
|
||||
PyGObject==3.42.2
|
||||
pyparsing==2.4.7
|
||||
pyxdg==0.28
|
||||
requests==2.28.2
|
||||
rsa==4.7
|
||||
six==1.15.0
|
||||
SQLAlchemy==1.3.23
|
||||
urllib3==1.26.14
|
||||
Werkzeug==1.0.1
|
||||
WTForms==2.3.3
|
|
@ -0,0 +1,8 @@
|
|||
html {
|
||||
margin: 0em;
|
||||
padding: 0em;
|
||||
}
|
||||
|
||||
ol, ul, li {
|
||||
list-style: none;
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
#controls, #fullPathHeader, #dynDiv,
|
||||
.errorStyling, .dirStyle, .movieStyle, .fileStyle {
|
||||
background-color: rgba(0,0,0,0.2);
|
||||
}
|
|
@ -0,0 +1,304 @@
|
|||
/* IDs */
|
||||
|
||||
#DIRPATHUL {
|
||||
display: none;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
#video,
|
||||
#bg {
|
||||
position: fixed;
|
||||
top: 0%;
|
||||
left: 0%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -999;
|
||||
}
|
||||
|
||||
#video,
|
||||
#bg img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -999;
|
||||
}
|
||||
|
||||
#video {
|
||||
position: fixed;
|
||||
display: none;
|
||||
background-color: rgba(0, 0, 0, 1);
|
||||
}
|
||||
|
||||
#controls, #dynUl,
|
||||
.errorStyling, .dirStyle, .movieStyle, .fileStyle {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
overflow: auto;
|
||||
padding-bottom: 0.5em;
|
||||
color: #ffffff;
|
||||
text-align: center;
|
||||
font-size: 1.2em;
|
||||
background-color: rgba(0,0,0,0.64);
|
||||
}
|
||||
|
||||
#favesList {
|
||||
border-style: solid;
|
||||
border-color: rgba(0, 0, 0, 0.5);
|
||||
border-width: 0.2em;
|
||||
background-color: rgba(7, 150, 159, 0.8);
|
||||
position: fixed;
|
||||
font-size: 2em;
|
||||
overflow-x: auto;
|
||||
overflow-y: scroll;
|
||||
padding: 1.5em;
|
||||
max-height: 632px;
|
||||
color: #ffffff;
|
||||
z-index: 888;
|
||||
}
|
||||
|
||||
#favesList > li:hover {
|
||||
cursor: pointer;
|
||||
background-color: rgba(92, 199, 35, 0.8);
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
#controls {
|
||||
display: block;
|
||||
position: fixed;
|
||||
z-index: 999;
|
||||
top: 0em;
|
||||
}
|
||||
|
||||
#dynUl {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(18em, 1fr));
|
||||
grid-column-gap: 1em;
|
||||
grid-row-gap: 1em;
|
||||
margin: 5em auto;
|
||||
width: 85%;
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
#imgView, #imgArea, #fileView {
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
}
|
||||
|
||||
#imgView, #fileView {
|
||||
position: fixed;
|
||||
bottom: 0em;
|
||||
z-index: 100;
|
||||
border-style: solid;
|
||||
border-color: rgb(114,184,199);
|
||||
|
||||
}
|
||||
|
||||
#fileView {
|
||||
display: block;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#fileViewInner {
|
||||
position: sticky;
|
||||
display: inline;
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
#imgArea {
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
#imgView {
|
||||
overflow: hidden;
|
||||
left: 15em;
|
||||
}
|
||||
|
||||
#NewItem {
|
||||
background-color: #ffffff;
|
||||
color: #000000;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#popOutControls {
|
||||
position: fixed;
|
||||
top: 15%;
|
||||
width: 99%;
|
||||
height: 15em;
|
||||
padding-top: 6em;
|
||||
opacity: 0.94;
|
||||
background: radial-gradient(circle,#3f3f3f,#000000);
|
||||
color: #ffffff;
|
||||
text-align: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
#serverMsgView {
|
||||
position: fixed;
|
||||
bottom: 0em;
|
||||
height: 5em;
|
||||
overflow-y: scroll;
|
||||
width: 100%;
|
||||
background-color: rgba(0,0,0,0.64);
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
#searchField {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#searchField:focus {
|
||||
height: 2em;
|
||||
border-style: solid;
|
||||
border-width: thin;
|
||||
border-color: rgba(55, 204, 209, 1);
|
||||
}
|
||||
|
||||
/* Classes */
|
||||
.imgViewImg {
|
||||
width: inherit;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
|
||||
.dirStyle { background-color: rgba(0, 0, 0, 0.56); }
|
||||
.movieStyle, .fileStyle { background-color: rgba(101, 101, 101, 0.56); }
|
||||
|
||||
.movieStyle {
|
||||
min-height: 6.5em;
|
||||
overflow: hidden;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.videoInputField {
|
||||
width: 100%;
|
||||
margin-top: 5.5em;
|
||||
background-color: rgba(0, 0, 0, 0.64);
|
||||
color: rgb(255, 255, 255);
|
||||
text-align: center;
|
||||
border-top: 1px solid rgb(255, 255, 255);
|
||||
border-bottom: 1px solid rgb(255, 255, 255);
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.dirStyle:hover, .movieStyle:hover, .fileStyle:hover {
|
||||
background-color: rgba(0, 141, 166, 0.56);
|
||||
cursor: pointer;
|
||||
box-shadow: 0px 0px 15px rgb(114,184,199);
|
||||
border-radius: 0.5em;
|
||||
}
|
||||
|
||||
.dirStyle:focus, .movieStyle:focus, .fileStyle:focus {
|
||||
background-color: rgba(0, 139, 35, 0.76);
|
||||
cursor: pointer;
|
||||
box-shadow: 0px 0px 25px rgb(114, 199, 120);
|
||||
border-radius: 0.5em;
|
||||
}
|
||||
|
||||
.dirTitle, .fileTitle, .movieTitle {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
border-style: none;
|
||||
font-size: 75%;
|
||||
}
|
||||
|
||||
.dirTitle, .fileTitle {
|
||||
width: auto;
|
||||
background-color: #00000000;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.movieTitle {
|
||||
width: 18em;
|
||||
background-color: #ffffff00;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
width: 12em;
|
||||
height: 6.5em;
|
||||
}
|
||||
|
||||
.systemIcon {
|
||||
width: 2em;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.iconImg {
|
||||
width: 18em;
|
||||
height: 12em;
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
.popOutBttn, .closeBttn {
|
||||
float: right;
|
||||
z-index: 2;
|
||||
width: 4em;
|
||||
height: 4em;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: 4em; /* the same as your div height */
|
||||
background-color: rgba(0,0,0, 0.85);
|
||||
color: rgb(255,255,255);
|
||||
border-style:solid;
|
||||
border-color: rgb(255,255,255);
|
||||
}
|
||||
|
||||
.popOutBttnInner {
|
||||
float: right;
|
||||
z-index: 2;
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
text-align: center;
|
||||
background-color: rgba(0,0,0, 0.85);
|
||||
color: rgb(255,255,255);
|
||||
border-style:solid;
|
||||
border-color: rgb(255,255,255);
|
||||
}
|
||||
|
||||
|
||||
.completionBar {
|
||||
float:left;
|
||||
clear:left;
|
||||
height: 0.1em;
|
||||
background-color: rgba(25, 125, 10, 1.0);
|
||||
}
|
||||
|
||||
/* Hover events */
|
||||
.dirTitle:hover,
|
||||
.iconImg:hover,
|
||||
.closeBttn:hover,
|
||||
.popOutBttnInner:hover,
|
||||
.popOutBttn:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.popOutBttnInner:hover,
|
||||
.popOutBttn:hover,
|
||||
.closeBttn:hover {
|
||||
background-color: rgba(255,255,255, 0.85);
|
||||
color: #000000;
|
||||
border-color: #000000;
|
||||
}
|
||||
|
||||
/* Messages coloring */
|
||||
.error, .warnning, .success {
|
||||
float: left;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.error { color: rgb(255, 0, 0); }
|
||||
.warning { color: rgb(255, 168, 0); }
|
||||
.success { color: rgb(136, 204, 39); }
|
Before Width: | Height: | Size: 216 KiB After Width: | Height: | Size: 216 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 7.4 KiB |
|
@ -0,0 +1 @@
|
|||
Place Holder File
|
|
@ -0,0 +1,54 @@
|
|||
// SSE events if supported
|
||||
if(typeof(EventSource) !== "undefined") {
|
||||
let source = new EventSource("resources/php/sse.php");
|
||||
source.onmessage = (event) => {
|
||||
if (event.data === "updateListing") {
|
||||
getDir("./");
|
||||
}
|
||||
};
|
||||
} else {
|
||||
console.log("SSE Not Supported In Browser...");
|
||||
}
|
||||
|
||||
const getFavesList = () => {
|
||||
doAjax("resources/php/dbController.php", "getTabs=true");
|
||||
}
|
||||
|
||||
const doAjax = async (actionPath, data) => {
|
||||
let xhttp = new XMLHttpRequest();
|
||||
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState === 4 && this.status === 200) {
|
||||
// Send the returned data to further process
|
||||
if (this.responseText != null) {
|
||||
handleJSONReturnData(JSON.parse(this.responseText));
|
||||
} else {
|
||||
document.getElementById('dynUl').innerHTML =
|
||||
"<p class=\"error\" style=\"width:100%;text-align:center;\"> "
|
||||
+ "No content returned. Check the folder path.</p>";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
xhttp.open("POST", actionPath, true);
|
||||
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
||||
xhttp.overrideMimeType('application/json'); // Force return to be JSON
|
||||
xhttp.send(data);
|
||||
}
|
||||
|
||||
const fileUploader = (data) => {
|
||||
let xhttp = new XMLHttpRequest();
|
||||
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState === 4 && this.status === 200) {
|
||||
// Send the returned data to further process
|
||||
if (this.responseXML != null) {
|
||||
handleXMLReturnData(this.responseXML);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
xhttp.open("POST", "resources/php/filesystemActions.php", true);
|
||||
xhttp.overrideMimeType('application/xml'); // Force return to be XML
|
||||
xhttp.send(data);
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
const getCookie = (cname) => {
|
||||
let decodedCookie = decodeURIComponent(document.cookie);
|
||||
let name = cname + "=";
|
||||
let ca = decodedCookie.split(';');
|
||||
for(let i = 0; i <ca.length; i++) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) == ' ') {
|
||||
c = c.substring(1);
|
||||
}
|
||||
if (c.indexOf(name) == 0) {
|
||||
return c.substring(name.length, c.length);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
const faveManager = (elm) => {
|
||||
let path = document.getElementById("path").innerHTML;
|
||||
let data = "";
|
||||
|
||||
if (elm.style.backgroundColor != "") {
|
||||
elm.style.backgroundColor = "";
|
||||
elm.style.color = "";
|
||||
data = "deleteLink=true";
|
||||
} else {
|
||||
elm.style.backgroundColor = "rgb(255, 255, 255)";
|
||||
elm.style.color = "rgb(0, 0, 0)";
|
||||
data = "deleteLink=false";
|
||||
}
|
||||
|
||||
data += "&linkPath=" + path;
|
||||
doAjax("resources/php/dbController.php", data);
|
||||
}
|
||||
|
||||
// Basically resetting path nodes and setting them up
|
||||
// to the new path and just doing a refresh
|
||||
const loadFave = (elm) => {
|
||||
let path = elm.getAttribute("name");
|
||||
let parts = path.split("/");
|
||||
let size = parts.length;
|
||||
pathNodes = [];
|
||||
|
||||
pathNodes.push(parts[0] + "/");
|
||||
for (let i = 1; i < size - 1; i++) {
|
||||
pathNodes.push(parts[i] + "/");
|
||||
}
|
||||
pathNodes.push(parts[size - 1]);
|
||||
|
||||
getDir("./");
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
let binary = null;
|
||||
let pathNodes = [];
|
||||
|
||||
const lockFolders = () => {
|
||||
const data = "lockFolders=true";
|
||||
doAjax("resources/php/lockedFolders.php", data);
|
||||
getDir("./");
|
||||
}
|
||||
|
||||
const getDir = (query) => {
|
||||
document.getElementById("controls").style.opacity = "1";
|
||||
document.getElementById("dynUl").style.display = "grid";
|
||||
document.getElementById("video").src = "#";
|
||||
document.getElementById("video").style.display = "none";
|
||||
|
||||
let formUlPth = document.getElementById("DIRPATHUL");
|
||||
let mergeType = document.getElementById("MergeType");
|
||||
let passwd = undefined;
|
||||
let data = "";
|
||||
let cookies = "";
|
||||
let dirCookie = "";
|
||||
|
||||
// push or pop to path list
|
||||
if (query === "/") {
|
||||
// Process path from cookie and set to array/list
|
||||
dirCookie = getCookie("dirQuery");
|
||||
if (dirCookie != "" && dirCookie != "./") {
|
||||
dirCookie = dirCookie.split("/");
|
||||
dirCookie.pop(); // account for ending empty slot
|
||||
|
||||
let size = dirCookie.length;
|
||||
for (var i = 0; i < size; i++) {
|
||||
pathNodes.push(dirCookie[i] + "/");
|
||||
}
|
||||
} else {
|
||||
pathNodes = [];
|
||||
pathNodes.push("." + query);
|
||||
}
|
||||
} else if (query === "../") {
|
||||
// Only remove while not in root
|
||||
if (pathNodes.length > 1) {
|
||||
pathNodes.pop();
|
||||
}
|
||||
} else if (query === "./") {
|
||||
// Do nothing since re-scanning dir
|
||||
} else {
|
||||
pathNodes.push(query); // Add path
|
||||
}
|
||||
|
||||
// Create path from array of items
|
||||
for (pathNode of pathNodes) { data += pathNode; }
|
||||
|
||||
try {
|
||||
passwd = document.getElementById("PASSWD").value;
|
||||
} catch (e) {
|
||||
passwd = "";
|
||||
}
|
||||
|
||||
// Setup upload path for form and make a cookie for persistence during browser session....
|
||||
formUlPth.value = data;
|
||||
data = "dirQuery=" + encodeURIComponent(data);
|
||||
document.cookie = data + "; expires=Sun, 31 Dec 2034 12:00:00 UTC";
|
||||
data +="&mergeType=" + mergeType.checked
|
||||
+ "&passwd=" + passwd;
|
||||
|
||||
doAjax("resources/php/getDirList.php", data);
|
||||
}
|
||||
|
||||
const uploadFiles = async () => {
|
||||
let toUpload = document.getElementsByName("filesToUpload[]")[0];
|
||||
let path = document.getElementById("path").innerHTML;
|
||||
let reader = new FileReader();
|
||||
let data = new FormData();
|
||||
let size = toUpload.files.length;
|
||||
|
||||
data.append("UploadFiles", "trut");
|
||||
data.append("DIRPATHUL", path);
|
||||
|
||||
// Add files
|
||||
if (size > 0) {
|
||||
for (let i = 0; i < size; i++) {
|
||||
data.append("filesToUpload[]", toUpload.files[i]);
|
||||
}
|
||||
fileUploader(data);
|
||||
}
|
||||
}
|
||||
|
||||
const createItem = (type) => {
|
||||
let path = document.getElementById("path").innerHTML;
|
||||
let newItem = document.getElementById("NewItem");
|
||||
let fullPth = path + newItem.value;
|
||||
newItem.value = "";
|
||||
fullPth = encodeURIComponent(fullPth);
|
||||
|
||||
doAjax("resources/php/filesystemActions.php",
|
||||
"createItem=true&item=" + fullPth + "&type=" + type);
|
||||
}
|
||||
|
||||
const deleteItem = () => {
|
||||
let path = document.getElementById("path").innerHTML;
|
||||
// Clicked yes to delete and there is an item
|
||||
if (itemObj != undefined && itemObj != null) {
|
||||
let fullPth = path + itemObj;
|
||||
fullPth = encodeURIComponent(fullPth);
|
||||
let answer = confirm("Are you sure you want to delete: " + fullPth);
|
||||
if (answer == true) {
|
||||
doAjax("resources/php/filesystemActions.php",
|
||||
"deleteItem=true&item=" + fullPth);
|
||||
|
||||
console.log("Deleted: " + fullPth);
|
||||
itemObj = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const renameItem = (obj) => {
|
||||
let path = encodeURIComponent(document.getElementById("path").innerHTML);
|
||||
let oldName = encodeURIComponent(formerFileName);
|
||||
let newName = encodeURIComponent(obj.value);
|
||||
let formData = "renameItem=true&oldName=" + oldName + "&newName=" + newName + "&path=" + path;
|
||||
|
||||
console.log("Old name: " + oldName);
|
||||
console.log("New name: " + newName);
|
||||
|
||||
doAjax("resources/php/filesystemActions.php",
|
||||
formData);
|
||||
}
|
||||
|
||||
const openInLocalProg = (media) => {
|
||||
doAjax("resources/php/filesystemActions.php",
|
||||
"media=" + media);
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
const insertArea = document.getElementById('dynUl');
|
||||
|
||||
|
||||
const handleJSONReturnData = (data) => {
|
||||
if (data.message) {
|
||||
if (data.message.type == "locked") {
|
||||
createPassField();
|
||||
} else {
|
||||
const text = document.createTextNode(data.message.text)
|
||||
document.getElementById("serverMsgView").appendChild(text);
|
||||
}
|
||||
return ;
|
||||
}
|
||||
|
||||
if (data.list) {
|
||||
updateHTMLDirList(data);
|
||||
} else if (data.FAVES_LIST) {
|
||||
generateFavesList(data.FAVES_LIST);
|
||||
}
|
||||
}
|
||||
|
||||
const generateFavesList = (data) => {
|
||||
let listView = document.getElementById("favesList");
|
||||
listView.innerHTML = "";
|
||||
|
||||
data.forEach(fave => {
|
||||
let liTag = document.createElement("LI");
|
||||
let parts = (fave.includes("/")) ? fave.split("/") : fave.split("\\");
|
||||
let txtNode = document.createTextNode(parts[parts.length - 2]);
|
||||
|
||||
liTag.setAttribute("name", fave);
|
||||
liTag.setAttribute("title", fave);
|
||||
liTag.setAttribute("onclick", "loadFave(this)");
|
||||
liTag.appendChild(txtNode);
|
||||
listView.appendChild(liTag);
|
||||
});
|
||||
}
|
||||
|
||||
const updateHTMLDirList = async (data) => {
|
||||
var dirTemplate = document.querySelector('#dirTemplate');
|
||||
var vidTemplate = document.querySelector('#vidTemplate');
|
||||
var imgTemplate = document.querySelector('#imgTemplate');
|
||||
var filTemplate = document.querySelector('#filTemplate');
|
||||
let dirPath = data.PATH_HEAD;
|
||||
let isInFaves = data.IN_FAVE;
|
||||
let dirs = (data.list.dirs) ? data.list.dirs : [];
|
||||
let videos = (data.list.vids) ? data.list.vids : [];
|
||||
let images = (data.list.imgs) ? data.list.imgs : [];
|
||||
let files = (data.list.files) ? data.list.files : [];
|
||||
let i = 0;
|
||||
let size = 0;
|
||||
|
||||
document.getElementById("path").innerHTML = dirPath;
|
||||
insertArea.innerHTML = "";
|
||||
|
||||
// Setup background if there is a 000.* in selection
|
||||
let bgImgPth = images[0] ? images[0].image : "";
|
||||
if (bgImgPth.match(/000\.(jpg|png|gif)\b/) != null) {
|
||||
updateBG(dirPath + bgImgPth);
|
||||
} else {
|
||||
updateBG("resources/images/backgrounds/000.jpg");
|
||||
}
|
||||
|
||||
// determin whether to style faves or not
|
||||
let elm = document.getElementById("faves");
|
||||
if (isInFaves == "true") {
|
||||
elm.style.backgroundColor = "rgb(255, 255, 255)";
|
||||
elm.style.color = "rgb(0, 0, 0)";
|
||||
} else {
|
||||
elm.style.backgroundColor = "";
|
||||
elm.style.color = "";
|
||||
}
|
||||
|
||||
// Insert dirs
|
||||
let dirClone = document.importNode(dirTemplate.content, true);
|
||||
let dirImg = "resources/images/icons/folder.png";
|
||||
let dir = null;
|
||||
size = dirs.length;
|
||||
for (; i < size; i++) {
|
||||
dir = dirs[i].dir;
|
||||
const clone = dirClone.cloneNode(true);
|
||||
createElmBlock(clone, dirImg, dir);
|
||||
}
|
||||
|
||||
// Insert videos
|
||||
let vidClone = document.importNode(vidTemplate.content, true);
|
||||
let thumbnail = "";
|
||||
let title = "";
|
||||
size = videos.length;
|
||||
for (i = 0; i < size; i++) {
|
||||
title = videos[i].video.title;
|
||||
thumbnail = videos[i].video.thumbnail;
|
||||
const clone = vidClone.cloneNode(true);
|
||||
createElmBlock(clone, thumbnail, title, true, dirPath);
|
||||
}
|
||||
|
||||
// Insert images
|
||||
let imgClone = document.importNode(imgTemplate.content, true);
|
||||
thumbnail = "";
|
||||
size = images.length;
|
||||
for (i = 0; i < size; i++) {
|
||||
thumbnail = images[i].image;
|
||||
if (thumbnail.match(/000\.(jpg|png|gif)\b/) == null &&
|
||||
!thumbnail.includes("favicon.png")) {
|
||||
const clone = imgClone.cloneNode(true);
|
||||
let imgTag = clone.firstElementChild;
|
||||
imgTag.src = dirPath + '/' + thumbnail;
|
||||
imgTag.alt = thumbnail;
|
||||
insertArea.appendChild(clone);
|
||||
}
|
||||
}
|
||||
|
||||
// Insert files
|
||||
let fileClone = document.importNode(filTemplate.content, true);
|
||||
size = files.length;
|
||||
for (i = 0; i < size; i++) {
|
||||
const clone = fileClone.cloneNode(true);
|
||||
let fileName = files[i].file;
|
||||
createElmBlock(clone, setFileIconType(fileName), fileName);
|
||||
}
|
||||
}
|
||||
|
||||
const createElmBlock = (elm, imgSrc, fileName, isVideo = null, path = null) => {
|
||||
contnrTag = elm.firstElementChild;
|
||||
let imgTag = null;
|
||||
let inputTag = elm.querySelector("input");
|
||||
|
||||
if (isVideo) {
|
||||
contnrTag.style = "background-image: url('/resources/images/thumbnails/" + imgSrc + "')";
|
||||
inputTag.className = "videoInputField";
|
||||
let fullMedia = path + fileName;
|
||||
elm.querySelector("span").addEventListener("click", function (eve) {
|
||||
openInLocalProg(fullMedia);
|
||||
});
|
||||
} else {
|
||||
imgTag = elm.querySelector("img");
|
||||
imgTag.src = imgSrc;
|
||||
imgTag.alt = fileName;
|
||||
}
|
||||
|
||||
contnrTag.title = fileName;
|
||||
inputTag.value = fileName;
|
||||
inputTag.addEventListener("focusout", function (eve) {
|
||||
disableEdits(eve.target);
|
||||
});
|
||||
insertArea.appendChild(elm);
|
||||
}
|
||||
|
||||
const setFileIconType = (fileName) => {
|
||||
if (fileName.match(/\.(doc|docx|xls|xlsx|rtf)\b/) != null) {
|
||||
return "resources/images/icons/doc.png";
|
||||
} else if (fileName.match(/\.(7z|7zip|zip|tar.gz|tar.xz|gz|rar|jar)\b/) != null) {
|
||||
return "resources/images/icons/arc.png";
|
||||
} else if (fileName.match(/\.(pdf)\b/) != null) {
|
||||
return "resources/images/icons/pdf.png";
|
||||
} else if (fileName.match(/\.(html)\b/) != null) {
|
||||
return "resources/images/icons/html.png";
|
||||
} else if (fileName.match(/\.(txt|conf)\b/) != null) {
|
||||
return "resources/images/icons/text.png";
|
||||
} else if (fileName.match(/\.(iso|img)\b/) != null) {
|
||||
return "resources/images/icons/img.png";
|
||||
} else if (fileName.match(/\.(sh|batch|exe)\b/) != null) {
|
||||
return "resources/images/icons/scrip.png";
|
||||
} else {
|
||||
return "resources/images/icons/bin.png";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
const createPassField = () => {
|
||||
let passField = document.createElement("INPUT");
|
||||
let submitBttn = document.createElement("BUTTON");
|
||||
passField.id = "PASSWD";
|
||||
passField.type = "password";
|
||||
passField.placeholder = "Password...";
|
||||
submitBttn.innerHTML = "Submit";
|
||||
insertArea.innerHTML = "";
|
||||
|
||||
passField.onkeyup = (eve) => {
|
||||
if (eve.key == "Enter") {
|
||||
getDir("./");
|
||||
}
|
||||
};
|
||||
|
||||
submitBttn.onclick = () => {
|
||||
getDir("./");
|
||||
};
|
||||
|
||||
insertArea.appendChild(passField);
|
||||
insertArea.appendChild(submitBttn);
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
let formerFileName = "";
|
||||
|
||||
const tgglElmView = (id) => {
|
||||
let elm = document.getElementById(id);
|
||||
if (elm.style.display == "none") {
|
||||
elm.style.display = "block";
|
||||
} else {
|
||||
elm.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
const searchPage = (elm) => {
|
||||
let query = elm.value.toLowerCase();
|
||||
let list = document.getElementById("dynUl").querySelectorAll("[title]");
|
||||
let size = list.length;
|
||||
|
||||
for (var i = 0; i < size; i++) {
|
||||
if (!list[i].title.toLowerCase().includes(query)) {
|
||||
list[i].style.display = "none";
|
||||
} else {
|
||||
list[i].style.display = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const clearSearch = () => {
|
||||
let list = document.getElementById("dynUl").querySelectorAll("[title]");
|
||||
let size = list.length;
|
||||
|
||||
for (var i = 0; i < size; i++) {
|
||||
list[i].style.display = "";
|
||||
}
|
||||
}
|
||||
|
||||
const enableEdit = (obj) => {
|
||||
obj.style.backgroundColor = "#ffffffff";
|
||||
obj.style.color = '#000000ff';
|
||||
obj.readOnly = '';
|
||||
formerFileName = obj.value;
|
||||
}
|
||||
|
||||
const disableEdits = (elm) => {
|
||||
elm.style.backgroundColor = "";
|
||||
elm.style.color = '';
|
||||
elm.value = formerFileName;
|
||||
elm.readOnly = "true";
|
||||
}
|
||||
|
||||
const showMedia = async (mediaLoc, type) => {
|
||||
let path = document.getElementById("path").innerHTML;
|
||||
let tempRef = mediaLoc.toLowerCase();
|
||||
let fullMedia = path + mediaLoc;
|
||||
|
||||
if (type === "video") {
|
||||
setupVideo(type, fullMedia, tempRef);
|
||||
} else {
|
||||
createFloatingPane(type, fullMedia);
|
||||
}
|
||||
}
|
||||
|
||||
const setupVideo = async (type, fullMedia, tempRef) => {
|
||||
try {
|
||||
let video = document.getElementById("video");
|
||||
video.autoplay = true;
|
||||
video.poster = "resources/images/loading.gif";
|
||||
|
||||
if ((/\.(mkv|avi|flv|mov|m4v|mpg|wmv|mpeg|mp4|mp3|webm|flac|ogg|pdf)$/i).test(tempRef)) {
|
||||
if ((/\.(mkv|avi|wmv)$/i).test(tempRef)) {
|
||||
const params = "remuxVideo=true&mediaPth=" + fullMedia;
|
||||
let response = await fetch("resources/php/filesystemActions.php",
|
||||
{method: "POST", body: new URLSearchParams(params)});
|
||||
let xml = new window.DOMParser().parseFromString(await response.text(), "text/xml");
|
||||
|
||||
if (xml.getElementsByTagName("REMUX_PATH")[0]) {
|
||||
fullMedia = xml.getElementsByTagName("REMUX_PATH")[0].innerHTML;
|
||||
} else {
|
||||
return ;
|
||||
}
|
||||
} else if ((/\.(avi|flv|mov|m4v|mpg|wmv)$/i).test(tempRef)) {
|
||||
openInLocalProg(fullMedia);
|
||||
return ;
|
||||
}
|
||||
}
|
||||
|
||||
// This is questionable in usage since it loads the full video
|
||||
// before showing; but, seeking doesn't work otherwise...
|
||||
let response = await fetch(fullMedia, {method: "GET"});
|
||||
var vidSrc = URL.createObjectURL(await response.blob()); // IE10+
|
||||
video.src = vidSrc;
|
||||
|
||||
document.getElementById("controls").style.opacity = "0";
|
||||
document.getElementById("video").style.display = "block";
|
||||
document.getElementById("dynUl").style.display = "none";
|
||||
} catch (e) {
|
||||
document.getElementById("controls").style.opacity = "1";
|
||||
document.getElementById("dynUl").style.display = "grid";
|
||||
document.getElementById("video").src = "#";
|
||||
document.getElementById("video").style.display = "none";
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const createFloatingPane = (type, fullMedia) => {
|
||||
let iframe = document.createElement("IFRAME");
|
||||
let outterDiv = document.createElement("DIV");
|
||||
let popOutDiv = document.createElement("DIV");
|
||||
let closeDiv = document.createElement("DIV");
|
||||
let toLocDiv = document.createElement("DIV");
|
||||
let imgDiv = document.createElement("DIV");
|
||||
let aTag = document.createElement("A");
|
||||
let imgTag = document.createElement("IMG");
|
||||
let closeText = document.createTextNode("X");
|
||||
|
||||
closeDiv.className = "closeBttn";
|
||||
closeDiv.title = "Close";
|
||||
closeDiv.setAttribute("onclick", "closeContainer(this)");
|
||||
closeDiv.appendChild(closeText);
|
||||
|
||||
aTag.title = "New Tab";
|
||||
aTag.target = "_blank";
|
||||
aTag.href = fullMedia;
|
||||
|
||||
popOutDiv.className = "popOutBttn";
|
||||
popOutDiv.innerHTML = "↗";
|
||||
aTag.appendChild(popOutDiv);
|
||||
|
||||
toLocDiv.title = "Open In Local Program";
|
||||
toLocDiv.className = "popOutBttn";
|
||||
toLocDiv.innerHTML = "∽";
|
||||
toLocDiv.setAttribute("onclick", "openInLocalProg('" + fullMedia + "')");
|
||||
|
||||
imgDiv.id = "imgArea";
|
||||
imgTag.className = "imgViewImg";
|
||||
imgTag.src = fullMedia;
|
||||
imgDiv.appendChild(imgTag);
|
||||
|
||||
iframe.id = "fileViewInner";
|
||||
iframe.src = fullMedia;
|
||||
|
||||
outterDiv.appendChild(closeDiv);
|
||||
outterDiv.appendChild(aTag);
|
||||
outterDiv.appendChild(toLocDiv);
|
||||
|
||||
if (type === "image") {
|
||||
outterDiv.id = "imgView";
|
||||
outterDiv.appendChild(imgDiv);
|
||||
} else {
|
||||
outterDiv.id = "fileView";
|
||||
outterDiv.appendChild(iframe);
|
||||
}
|
||||
|
||||
document.body.appendChild(outterDiv);
|
||||
dragContainer(outterDiv); // Set for dragging events
|
||||
}
|
||||
|
||||
const closeContainer = (elm) => {
|
||||
elm.parentElement.parentElement.removeChild(elm.parentElement);
|
||||
}
|
||||
|
||||
const clearDirCookie = () => {
|
||||
let expireDate = "Thu, 01 Jan 1970 00:00:00 UTC";
|
||||
document.cookie = "dirQuery=; expires=" + expireDate;
|
||||
getDir("/");
|
||||
}
|
||||
|
||||
const downloadItem = () => {
|
||||
let partialPath = document.getElementById("path").innerHTML;
|
||||
let brTag = document.createElement("BR");
|
||||
let aTag = document.createElement("A");
|
||||
let text = document.createTextNode(itemObj);
|
||||
let fullPath = partialPath + itemObj;
|
||||
aTag.setAttribute("href", fullPath);
|
||||
aTag.setAttribute("target", "_blank");
|
||||
aTag.setAttribute("id", itemObj);
|
||||
aTag.append(text);
|
||||
|
||||
document.getElementById("serverMsgView").append(aTag, brTag);
|
||||
aTag.click();
|
||||
}
|
||||
|
||||
const clearDlList = () => { document.getElementById("CLEARBTTN").click(); }
|
||||
const onloadSetBG = () => { updateBG("resources/images/backgrounds/000.jpg"); }
|
||||
|
||||
const updateBG = (bgImg) => {
|
||||
try {
|
||||
document.getElementById("bg").src = bgImg;
|
||||
} catch (e) { }
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
let itemObj = undefined;
|
||||
let interval = undefined;
|
||||
let cursorX;
|
||||
let cursorY;
|
||||
|
||||
document.getElementById("controls").onmouseover = (eve) => {
|
||||
let source = document.getElementById("video").src;
|
||||
let target = eve.target
|
||||
|
||||
if (interval)
|
||||
clearInterval(interval);
|
||||
|
||||
if (source !== "#") {
|
||||
eve.target.style.opacity = "1";
|
||||
document.getElementById("dynUl").style.display = "grid";
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("video").onmouseover = (eve) => {
|
||||
interval = setInterval(function () {
|
||||
elementMouseIsOver = document.elementFromPoint(cursorX, cursorY);
|
||||
if (elementMouseIsOver.tagName == "BODY" ||
|
||||
elementMouseIsOver.id == "video") {
|
||||
let controls = document.getElementById("controls");
|
||||
controls.style.opacity = "0";
|
||||
document.getElementById("dynUl").style.display = "none";
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
// For context menu to have element
|
||||
document.onclick = (event) => {
|
||||
let obj = event.target;
|
||||
let callingID = obj.id;
|
||||
let classNM = obj.className;
|
||||
|
||||
// right-click detect
|
||||
if (event.which == 3) {
|
||||
if (callingID == "imageID") {
|
||||
setSelectedItem(obj.alt);
|
||||
} else if (callingID == "dirID" || callingID == "fileID" ||
|
||||
callingID == "movieID") {
|
||||
let node = obj.parentNode;
|
||||
setSelectedItem(node.children[1].value);
|
||||
} else if (classNM == "fileStyle" || classNM == "dirStyle" ||
|
||||
classNM == "movieStyle") {
|
||||
setSelectedItem(obj.children[1].value);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Actions for content
|
||||
document.ondblclick = (event) => {
|
||||
let obj = event.target;
|
||||
let callingID = obj.id;
|
||||
let classNM = obj.className;
|
||||
|
||||
// Left click detect
|
||||
if (event.which == 1) {
|
||||
// If clicking on container
|
||||
if (classNM === "fileStyle" || classNM === "movieStyle" ||
|
||||
classNM === "dirStyle") {
|
||||
if (classNM === "dirStyle") {
|
||||
getDir(obj.children[1].value);
|
||||
} else if (classNM === "movieStyle") {
|
||||
showMedia(obj.title, "video");
|
||||
} else {
|
||||
showMedia(obj.children[1].value, "file");
|
||||
}
|
||||
} else if (callingID === "dirID") { // If clicking on dir icon
|
||||
let node = obj.parentNode;
|
||||
getDir(node.children[1].value);
|
||||
} else if (callingID === "movieID") { // If clicking on movie thumbnail
|
||||
let node = obj.parentNode;
|
||||
showMedia(node.children[1].value, "video");
|
||||
} else if (callingID === "imageID") { // If clicking on image
|
||||
showMedia(obj.alt, "image");
|
||||
} else if (callingID === "titleID") { // If clicking on text title
|
||||
enableEdit(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mainly for rename event
|
||||
document.onkeydown = (event) => {
|
||||
let obj = event.target;
|
||||
let callingID = event.target.id;
|
||||
let keyCodeVal = event.keyCode;
|
||||
|
||||
// If keycode == Enter
|
||||
if (keyCodeVal == 13) {
|
||||
if (callingID == "titleID") {
|
||||
renameItem(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const setSelectedItem = (item) => { itemObj = item; }
|
||||
|
||||
// Drage event for the poped out image and media container
|
||||
const dragContainer = (elmnt) => {
|
||||
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
|
||||
elmnt.onmousedown = dragMouseDown;
|
||||
|
||||
function dragMouseDown(e) {
|
||||
e = e || window.event;
|
||||
pauseEvent(e);
|
||||
// get the mouse cursor position at startup:
|
||||
pos3 = e.clientX;
|
||||
pos4 = e.clientY;
|
||||
document.onmouseup = closeDragElement;
|
||||
// call a function whenever the cursor moves:
|
||||
document.onmousemove = elementDrag;
|
||||
}
|
||||
|
||||
function elementDrag(e) {
|
||||
e = e || window.event;
|
||||
pauseEvent(e);
|
||||
// calculate the new cursor position:
|
||||
pos1 = pos3 - e.clientX;
|
||||
pos2 = pos4 - e.clientY;
|
||||
pos3 = e.clientX;
|
||||
pos4 = e.clientY;
|
||||
// set the element's new position:
|
||||
elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
|
||||
elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
|
||||
}
|
||||
|
||||
function closeDragElement(e) {
|
||||
// stop moving when mouse button is released:
|
||||
document.onmouseup = null;
|
||||
document.onmousemove = null;
|
||||
}
|
||||
|
||||
function pauseEvent(e) {
|
||||
if(e.stopPropagation) e.stopPropagation();
|
||||
if(e.preventDefault) e.preventDefault();
|
||||
|
||||
e.cancelBubble=true;
|
||||
e.returnValue=false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Mouse position detection for control show/hide setup
|
||||
document.onmousemove = function(e){
|
||||
cursorX = e.pageX;
|
||||
cursorY = e.pageY;
|
||||
}
|
||||
|
||||
setInterval(checkCursor, 2000);
|
||||
function checkCursor() {
|
||||
return "";
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
$MEDIAPLAYER = "mpv ";
|
||||
$MPLAYER_WH = " -xy 1600 -geometry 50%:50% ";
|
||||
$MUSICPLAYER = "/opt/deadbeef/bin/deadbeef";
|
||||
$IMGVIEWER = "mirage";
|
||||
$OFFICEPROG = "libreoffice";
|
||||
$PDFVIEWER = "evince";
|
||||
$TEXTVIEWER = "leafpad";
|
||||
$FILEMANAGER = "spacefm";
|
||||
$LOCKPASSWORD = "1234";
|
||||
$TMPFOLDERSIZE = 8000; // tmp folder size check for cleanup if above 8GB used.
|
||||
// NOTE: Split folders with ::::
|
||||
$LOCKEDFOLDERS = "./dirLockCheck/";
|
||||
|
||||
?>
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
include_once 'serverMessenger.php';
|
||||
|
||||
chdir("../../");
|
||||
$db = new SQLite3('resources/db/webfm.db');
|
||||
if($db === false){
|
||||
$message = "Server: [Error] --> Database connection failed!";
|
||||
serverMessage("error", $message);
|
||||
}
|
||||
?>
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
include_once 'connection.php';
|
||||
include_once 'serverMessenger.php';
|
||||
|
||||
function getTabLinks() {
|
||||
GLOBAL $db;
|
||||
|
||||
$res = $db->query('Select * FROM faves');
|
||||
$GeneratedJSON = array('FAVES_LIST' => array());
|
||||
while ($row = $res->fetchArray(SQLITE3_ASSOC)) {
|
||||
$GeneratedJSON['FAVES_LIST'][] = $row['link'];
|
||||
}
|
||||
|
||||
echo json_encode($GeneratedJSON);
|
||||
}
|
||||
|
||||
function manageLink($ACTION, $PATH) {
|
||||
GLOBAL $db;
|
||||
$ACTION_TYPE = "";
|
||||
|
||||
// If action isn't true then we add else we delete or exit.
|
||||
if ($ACTION == "false") {
|
||||
$stmt = $db->prepare('INSERT INTO faves VALUES(:link)');
|
||||
$ACTION_TYPE = "added to";
|
||||
} elseif ($ACTION == "true") {
|
||||
$stmt = $db->prepare('DELETE FROM faves WHERE link = :link');
|
||||
$ACTION_TYPE = "deleted from";
|
||||
} else {
|
||||
$message = "Server: [Error] --> Action for adding or deleting isn't set properly!";
|
||||
serverMessage("error", $message);
|
||||
return;
|
||||
}
|
||||
|
||||
$stmt->bindValue(":link", $PATH, SQLITE3_TEXT);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
$message = "Server: [Success] --> Fave link: " .
|
||||
$PATH . " " . $ACTION_TYPE . " the database!";
|
||||
serverMessage("success", $message);
|
||||
}
|
||||
|
||||
|
||||
// Determin action
|
||||
chdir("../../");
|
||||
if (isset($_POST['getTabs'])) {
|
||||
getTabLinks();
|
||||
} elseif (isset($_POST['deleteLink'],
|
||||
$_POST['linkPath'])) {
|
||||
manageLink($_POST['deleteLink'], $_POST['linkPath']);
|
||||
} else {
|
||||
$message = "Server: [Error] --> Illegal Access Method!";
|
||||
serverMessage("error", $message);
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,199 @@
|
|||
<?php
|
||||
session_start();
|
||||
include_once 'serverMessenger.php';
|
||||
|
||||
// Create file or folder
|
||||
function createItem($FILE, $TYPE) {
|
||||
$FILE = trim($FILE);
|
||||
$FILE = preg_replace('/\.*$/','',$FILE); // removing dot . after file extension
|
||||
|
||||
if ($TYPE === "dir"){
|
||||
mkdir($FILE, 0755);
|
||||
} else if ($TYPE === "file") {
|
||||
$myfile = fopen($FILE, "w");
|
||||
fclose($myfile);
|
||||
} else {
|
||||
$message = "Server: [Error] --> Failed to create folder or file!";
|
||||
serverMessage("error", $message);
|
||||
return;
|
||||
}
|
||||
|
||||
$message = "Server: [Success] --> The file " . $FILE . " has been created.";
|
||||
serverMessage("success", $message);
|
||||
$_SESSION["refreshState"] = "updateListing";
|
||||
}
|
||||
|
||||
// File or folder delition
|
||||
function deleteItem($FILE) {
|
||||
if (filetype($FILE) == "dir"){
|
||||
//GLOB_MARK adds a slash to directories returned
|
||||
$files = glob($FILE . '*', GLOB_MARK);
|
||||
foreach ($files as $file) {
|
||||
deleteItem($file);
|
||||
}
|
||||
rmdir($FILE);
|
||||
} else if (filetype($FILE) == "file") {
|
||||
unlink($FILE);
|
||||
} else {
|
||||
$message = "Server: [Error] --> Failed to delete item! Not a folder or file!";
|
||||
serverMessage("error", $message);
|
||||
return;
|
||||
}
|
||||
|
||||
$message = "Server: [Success] --> The file(s) has/have been deleted.";
|
||||
serverMessage("success", $message);
|
||||
$_SESSION["refreshState"] = "updateListing";
|
||||
}
|
||||
|
||||
// Rename file or folder
|
||||
function renameItem($OLDFILE, $NEWNAME, $PATH) {
|
||||
rename($PATH . $OLDFILE, $PATH . $NEWNAME);
|
||||
$message = "Server: [Success] --> The file " . $OLDFILE . " has been renamed to " . $NEWNAME . " side.";
|
||||
serverMessage("success", $message);
|
||||
$_SESSION["refreshState"] = "updateListing";
|
||||
}
|
||||
|
||||
// Uploader
|
||||
function uploadFiles($targetDir) {
|
||||
$numberOfFiles = count($_FILES['filesToUpload']['name']);
|
||||
|
||||
if ($numberOfFiles === 0) {
|
||||
$message = "Server: [Error] --> No files were uploaded!";
|
||||
serverMessage("error", $message);
|
||||
return;
|
||||
}
|
||||
|
||||
$type = "";
|
||||
$message = "";
|
||||
for ($i=0; $i < $numberOfFiles; $i++) {
|
||||
$uploadOk = 1;
|
||||
$fileName = $_FILES['filesToUpload']['name'][$i];
|
||||
$fileTmpName = $_FILES['filesToUpload']['tmp_name'][$i];
|
||||
|
||||
// Check if file already exists
|
||||
$targetFile = $targetDir . $fileName;
|
||||
if (file_exists($targetFile)) {
|
||||
if (filetype($targetFile) == "file") {
|
||||
unlink($targetFile);
|
||||
$message = "Server: [Warning] --> This file already exists. Overwriting it.";
|
||||
} else {
|
||||
$message = "Server: [Warning] --> This file might be a directory. Or, no files were submitted for uploading.";
|
||||
$uploadOk = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Check file size
|
||||
$fileSize = $_FILES['filesToUpload']['size'][$i];
|
||||
if ($fileSize > 500000000000) {
|
||||
$message = "Server: [Warning] --> This file is too large.";
|
||||
$uploadOk = 0;
|
||||
}
|
||||
|
||||
// Allow certain file formats
|
||||
// $ext = pathinfo($targetFile,PATHINFO_EXTENSION);
|
||||
// if(!preg_match('/^.*\.(rar|iso|img|tar|zip|7z|7zip|jpg|jpeg|png|gif|mpeg|mov|flv|avi|mp4|webm|mpg|mkv|m4a|mp3|ogg|docx|doc|odt|txt|pdf|)$/i', strtolower($ext))) {
|
||||
// $message = "Server: [Warning] --> This file type is not allowed.";
|
||||
// $uploadOk = 0;
|
||||
// }
|
||||
|
||||
// if everything is ok, try to upload file
|
||||
if ($uploadOk !== 0) {
|
||||
if (move_uploaded_file($fileTmpName, $targetFile)) {
|
||||
$type = "success";
|
||||
$message = "Server: [Success] --> The file " . $fileName . " has been uploaded.";
|
||||
$_SESSION["refreshState"] = "updateListing";
|
||||
}
|
||||
} else {
|
||||
$type = "error";
|
||||
$message .= "\nServer: [Error] --> Your file " . $fileName . " was not uploaded.";
|
||||
}
|
||||
}
|
||||
|
||||
serverMessage($type, $message);
|
||||
}
|
||||
|
||||
// Local program file access
|
||||
function openFile($FILE) {
|
||||
include 'config.php';
|
||||
$EXTNSN = strtolower(pathinfo($FILE, PATHINFO_EXTENSION));
|
||||
|
||||
if (preg_match('(mkv|avi|flv|mov|m4v|mpg|wmv|mpeg|mp4|webm)', $EXTNSN) === 1) {
|
||||
shell_exec($MEDIAPLAYER . "\"" . $FILE . "\" > /dev/null &");
|
||||
} else if (preg_match('(png|jpg|jpeg|gif)', $EXTNSN) === 1) {
|
||||
shell_exec($IMGVIEWER . ' "' . $FILE . '" > /dev/null &');
|
||||
} else if (preg_match('(psf|mp3|ogg|flac)', $EXTNSN) === 1) {
|
||||
shell_exec($MUSICPLAYER . ' "' . $FILE . '" > /dev/null &');
|
||||
} else if (preg_match('(odt|doc|docx|rtf)', $EXTNSN) === 1) {
|
||||
shell_exec($OFFICEPROG . ' "' . $FILE . '" > /dev/null &');
|
||||
} else if (preg_match('(txt)', $EXTNSN) === 1) {
|
||||
shell_exec($TEXTVIEWER . ' "' . $FILE . '" > /dev/null &');
|
||||
} else if (preg_match('(pdf)', $EXTNSN) === 1) {
|
||||
shell_exec($PDFVIEWER . ' "' . $FILE . '" > /dev/null &');
|
||||
}
|
||||
|
||||
$message = "Server: [Success] --> The file " . $FILE . " has been opened server side.";
|
||||
serverMessage("success", $message);
|
||||
}
|
||||
|
||||
function remuxVideo($FILE) {
|
||||
$FILE = trim($FILE);
|
||||
$PTH = "resources/tmp/";
|
||||
$HASHED_NAME = hash('sha256', $FILE) . '.mp4';
|
||||
$EXTNSN = strtolower(pathinfo($FILE, PATHINFO_EXTENSION));
|
||||
|
||||
|
||||
if (!file_exists($PTH . $HASHED_NAME)) {
|
||||
$io = popen('/usr/bin/du -sm ' . $PTH, 'r');
|
||||
$size = fgets($io, 4096);
|
||||
$size = (int) substr($size, 0, strpos ( $size, "\t" ));
|
||||
pclose ($io);
|
||||
|
||||
include 'config.php';
|
||||
if ($size > $TMPFOLDERSIZE) {
|
||||
$files = glob($PTH . '*');
|
||||
foreach($files as $file){
|
||||
if(is_file($file))
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
|
||||
if (preg_match('(mkv)', $EXTNSN) === 1)
|
||||
$COMMAND = 'ffmpeg -i "' . $FILE . '" -hide_banner -movflags +faststart -codec copy -strict -2 ' . $PTH . $HASHED_NAME;
|
||||
if (preg_match('(avi)', $EXTNSN) === 1)
|
||||
$COMMAND = 'ffmpeg -i "' . $FILE . '" -hide_banner -movflags +faststart -c:v libx264 -crf 21 -c:a aac -b:a 192k -ac 2 ' . $PTH . $HASHED_NAME;
|
||||
if (preg_match('(wmv)', $EXTNSN) === 1)
|
||||
$COMMAND = 'ffmpeg -i "' . $FILE . '" -hide_banner -movflags +faststart -c:v libx264 -crf 23 -c:a aac -strict -2 -q:a 100 ' . $PTH . $HASHED_NAME;
|
||||
|
||||
shell_exec($COMMAND . " 2> resources/vdata.txt");
|
||||
}
|
||||
|
||||
$GeneratedXML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
|
||||
$GeneratedXML .= "<REMUX_PATH>" . $PTH . $HASHED_NAME ."</REMUX_PATH>";
|
||||
echo $GeneratedXML;
|
||||
}
|
||||
|
||||
|
||||
chdir("../../");
|
||||
if (isset($_POST["remuxVideo"], $_POST["mediaPth"])) {
|
||||
remuxVideo($_POST["mediaPth"]);
|
||||
} else if (isset($_POST["createItem"],
|
||||
$_POST["item"],
|
||||
$_POST["type"])) {
|
||||
createItem($_POST["item"], $_POST["type"]);
|
||||
} else if (isset($_POST["deleteItem"], $_POST["item"])) {
|
||||
deleteItem($_POST["item"]);
|
||||
} else if (isset($_POST["renameItem"],
|
||||
$_POST["oldName"],
|
||||
$_POST["newName"],
|
||||
$_POST["path"])) {
|
||||
renameItem($_POST["oldName"], $_POST["newName"], $_POST["path"]);
|
||||
} else if(isset($_POST["UploadFiles"], $_POST["DIRPATHUL"])) {
|
||||
uploadFiles($_POST["DIRPATHUL"]);
|
||||
} else if (isset($_POST["media"])) {
|
||||
openFile($_POST["media"]);
|
||||
} else {
|
||||
$message = "Server: [Error] --> Incorrect access attempt!";
|
||||
serverMessage("error", $message);
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
session_start();
|
||||
include_once 'serverMessenger.php';
|
||||
|
||||
// Start of retrieving dir data
|
||||
function startListing($NEWPATH, $MERGESEASSONS, $PASSWD) {
|
||||
if (filetype($NEWPATH) == "dir") {
|
||||
include_once 'lockedFolders.php';
|
||||
|
||||
if (checkForLock($NEWPATH, $PASSWD) == false) {
|
||||
$subPath = ""; // This is used for season scanning as a means of properly getting
|
||||
// the video src.... It's left blank when not in a sub dir
|
||||
|
||||
$GeneratedJSON = array('PATH_HEAD' => $NEWPATH,
|
||||
'IN_FAVE' => isInDBCheck($NEWPATH),
|
||||
'list' => array()
|
||||
);
|
||||
|
||||
listDir($GeneratedJSON, $NEWPATH, $MERGESEASSONS, $subPath);
|
||||
echo json_encode($GeneratedJSON);
|
||||
} else {
|
||||
$message = "Server: [Error] --> Folder is locked.";
|
||||
serverMessage("locked", $message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function listDir(&$GeneratedJSON, &$NEWPATH, &$MERGESEASSONS, &$subPath) {
|
||||
if ($MERGESEASSONS !== "true") {
|
||||
$files = array_diff(scandir($NEWPATH), array('..', '.', 'resources'));
|
||||
foreach ($files as $fileName) {
|
||||
$fullPath = $NEWPATH . '/' . $fileName;
|
||||
// error_log($fullPath, 4);
|
||||
processItem($GeneratedJSON, $fullPath, $fileName, $subPath);
|
||||
}
|
||||
} else {
|
||||
$files = array_diff(scandir($NEWPATH), array('..', '.', 'resources'));
|
||||
foreach ($files as $fileName) {
|
||||
$fullPath = $NEWPATH . $fileName;
|
||||
// error_log($fullPath, 4);
|
||||
if (filetype($fullPath) == "dir" && strpos(strtolower($fileName),
|
||||
'season') !== false) {
|
||||
$fileName .= "/";
|
||||
listDir($GeneratedJSON, $fullPath, $MERGESEASSONS, $fileName);
|
||||
} else {
|
||||
processItem($GeneratedJSON, $fullPath, $fileName, $subPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign JSON Markup based on file type
|
||||
function processItem(&$GeneratedJSON, &$fullPath, &$fileName, $subPath) {
|
||||
if (preg_match('/^.*\.(mkv|avi|flv|mov|m4v|mpg|wmv|mpeg|mp4|webm)$/i', strtolower($fileName))) {
|
||||
$NAMEHASH = hash('sha256', $fileName);
|
||||
if (!file_exists('resources/images/thumbnails/' . $NAMEHASH . '.jpg')) {
|
||||
shell_exec('resources/ffmpegthumbnailer -t 65% -s 320 -c jpg '
|
||||
. '-i "' . $subPath . $fullPath . '" '
|
||||
. '-o resources/images/thumbnails/' . $NAMEHASH . '.jpg'
|
||||
);
|
||||
}
|
||||
|
||||
$GeneratedJSON['list']['vids'][] = array('video' =>
|
||||
array('title' => $subPath . $fileName,
|
||||
'thumbnail' => $NAMEHASH . '.jpg'
|
||||
)
|
||||
);
|
||||
} elseif (preg_match('/^.*\.(png|jpg|gif|jpeg)$/i', strtolower($fileName))) {
|
||||
$GeneratedJSON['list']['imgs'][] = array('image' => $subPath . $fileName);
|
||||
} elseif (filetype($fullPath) == "dir") {
|
||||
$GeneratedJSON['list']['dirs'][] = array('dir' => $fileName . "/");
|
||||
} else {
|
||||
$GeneratedJSON['list']['files'][] = array('file' => $subPath . $fileName);
|
||||
}
|
||||
}
|
||||
|
||||
function isInDBCheck($PATH) {
|
||||
$db = new SQLite3('resources/db/webfm.db');
|
||||
|
||||
if($db === false){
|
||||
$message = "Server: [Error] --> Database connection failed!";
|
||||
serverMessage("error", $message);
|
||||
die("ERROR: Could not connect to db.");
|
||||
}
|
||||
|
||||
$stmt = $db->prepare('SELECT 1 FROM faves WHERE link = :link');
|
||||
$stmt->bindValue(":link", $PATH, SQLITE3_TEXT);
|
||||
$result = $stmt->execute() ;
|
||||
$row = $result->fetchArray() ;
|
||||
|
||||
if ($row > 0) {
|
||||
return "true";
|
||||
} else {
|
||||
return "false";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Determin action
|
||||
chdir("../../");
|
||||
if (isset($_POST['dirQuery'])) {
|
||||
startListing(trim($_POST['dirQuery']), $_POST['mergeType'], $_POST['passwd']);
|
||||
} else {
|
||||
$message = "Server: [Error] --> Illegal Access Method!";
|
||||
serverMessage("error", $message);
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
// Check if sub folder is in locked folder
|
||||
function checkForLock($NEWPATH, $PASSWD) {
|
||||
include 'config.php';
|
||||
|
||||
$LOCKS = explode("::::", $LOCKEDFOLDERS);
|
||||
$size = sizeof($LOCKS);
|
||||
|
||||
if (isset($_SESSION["unlockState"]) && $_SESSION["unlockState"] == true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $size; $i++) {
|
||||
if (strpos($NEWPATH, $LOCKS[$i]) !== false) {
|
||||
if ($PASSWD === $LOCKPASSWORD) {
|
||||
$_SESSION["unlockState"] = true;
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function lockFolders() {
|
||||
session_start();
|
||||
include 'serverMessenger.php';
|
||||
|
||||
if (isset($_SESSION["unlockState"]) && $_SESSION["unlockState"] == true) {
|
||||
$_SESSION["unlockState"] = false;
|
||||
$message = "Server: [Success] --> Folders unlocked!";
|
||||
serverMessage("success", $message);
|
||||
} else {
|
||||
$message = "Server: [Warning] --> Folders aren't unlocked!"
|
||||
. "\n" . $_SESSION["unlockState"];
|
||||
serverMessage("warning", $message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (isset($_POST['lockFolders'])) {
|
||||
lockFolders();
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
function serverMessage($TYPE, $MESSAGE) {
|
||||
$GeneratedJSON = array( 'message' =>
|
||||
array(
|
||||
'type' => $TYPE,
|
||||
'text' => $MESSAGE
|
||||
)
|
||||
);
|
||||
|
||||
echo json_encode($GeneratedJSON);
|
||||
}
|
||||
?>
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
// Start the session
|
||||
session_start();
|
||||
include_once 'config.php';
|
||||
|
||||
if (!isset($_SESSION["refreshState"])) {
|
||||
$_SESSION["refreshState"] = "none";
|
||||
}
|
||||
|
||||
header('Content-Type: text/event-stream');
|
||||
header('Cache-Control: no-cache');
|
||||
echo "data:" . $_SESSION["refreshState"] . "\n\n";
|
||||
|
||||
$_SESSION["refreshState"] = "none";
|
||||
flush();
|
||||
?>
|
|
@ -0,0 +1,91 @@
|
|||
/*START OF HOMEPAGE BUTTON SETTINGS*/
|
||||
.homeSection {
|
||||
float: left;
|
||||
width: 40%;
|
||||
height: 14em;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
font-size: 100%;
|
||||
border-style: solid;
|
||||
border-width: .2em;
|
||||
color: rgba(0, 232, 255, .69); /*#00E8FF*/
|
||||
background: rgba(19, 21, 21, .6);
|
||||
}
|
||||
/*END OF HOMEPAGE BUTTON SETTINGS*/
|
||||
.header {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 99%;
|
||||
}
|
||||
.section, .lnksNdirs, .header {
|
||||
display: block;
|
||||
float: left;
|
||||
text-align: center;
|
||||
font-size: 100%;
|
||||
padding-top: 1em;
|
||||
margin: 1em;
|
||||
background: rgba(19, 21, 21, .7);
|
||||
color: rgba(0, 232, 255, .69); /*#00E8FF*/
|
||||
/* border-style: solid;
|
||||
border-width: .2em;
|
||||
*/
|
||||
}
|
||||
.backbutton {
|
||||
top: 3.55em;
|
||||
clear: both;
|
||||
z-index: 2;
|
||||
position: fixed;
|
||||
float: left;
|
||||
width: 5%;
|
||||
height: 50px;
|
||||
opacity: .8;
|
||||
}
|
||||
iframe {
|
||||
position: fixed;
|
||||
bottom: 2em;
|
||||
left: 25%;
|
||||
width: 50%;
|
||||
height: 360px;
|
||||
background: rgba(0,0,0,.5);
|
||||
transition: 0s;
|
||||
transition-delay:2s;
|
||||
}
|
||||
iframe:hover {
|
||||
left: 2%;
|
||||
width: 99%;
|
||||
height: 700px;
|
||||
transition-delay:2s;
|
||||
}
|
||||
.lnkStyl {
|
||||
clear: right;
|
||||
float: left;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.imgsStyl {
|
||||
margin-left: 50%;
|
||||
opacity: 0.699999988079071044921875;
|
||||
width: 32.333%;
|
||||
height: 20em;
|
||||
}
|
||||
.imgsStyl:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
#bg {
|
||||
position: fixed;
|
||||
top: 0%;
|
||||
left: 0%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
}
|
||||
#bg img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
body {
|
||||
width: 600%;
|
||||
}
|
||||
/*START OF HOMEPAGE BUTTON SETTINGS*/
|
||||
.homeSection {
|
||||
float: left;
|
||||
width: 40%;
|
||||
height: 14em;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
font-size: 100%;
|
||||
border-style: solid;
|
||||
border-width: .2em;
|
||||
color: rgba(0, 232, 255, .69); /*#00E8FF*/
|
||||
background: rgba(19, 21, 21, .6);
|
||||
}
|
||||
/*END OF HOMEPAGE BUTTON SETTINGS*/
|
||||
.header {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 99%;
|
||||
}
|
||||
.section, .lnksNdirs, .header {
|
||||
display: block;
|
||||
clear: right;
|
||||
float: left;
|
||||
text-align: center;
|
||||
font-size: 100%;
|
||||
padding-top: 1em;
|
||||
margin: 1em;
|
||||
background: rgba(19, 21, 21, .7);
|
||||
color: rgba(0, 232, 255, .69); /*#00E8FF*/
|
||||
/* border-style: solid;
|
||||
border-width: .2em;
|
||||
*/
|
||||
}
|
||||
.backbutton {
|
||||
top: 3.55em;
|
||||
clear: both;
|
||||
z-index: 2;
|
||||
position: fixed;
|
||||
float: left;
|
||||
width: 5%;
|
||||
height: 50px;
|
||||
opacity: .8;
|
||||
}
|
||||
iframe {
|
||||
position: fixed;
|
||||
bottom: 2em;
|
||||
left: 25%;
|
||||
width: 50%;
|
||||
height: 360px;
|
||||
background: rgba(0,0,0,.5)
|
||||
transition: 0s;
|
||||
transition-delay:2s;
|
||||
}
|
||||
.lnkStyl {
|
||||
clear: right;
|
||||
float: left;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.imgsStyl {
|
||||
margin-left: 50%;
|
||||
opacity: 0.699999988079071044921875;
|
||||
width: 32.333%;
|
||||
height: 20em;
|
||||
}
|
||||
.imgsStyl:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
#bg {
|
||||
position: fixed;
|
||||
top: 0%;
|
||||
left: 0%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
}
|
||||
#bg img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/*START OF HOMEPAGE BUTTON SETTINGS*/
|
||||
.homeSection {
|
||||
float: left;
|
||||
width: 40%;
|
||||
height: 14em;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
font-size: 100%;
|
||||
border-style: solid;
|
||||
border-width: .2em;
|
||||
color: rgba(0, 232, 255, .69); /*#00E8FF*/
|
||||
background: rgba(19, 21, 21, .6);
|
||||
}
|
||||
/*END OF HOMEPAGE BUTTON SETTINGS*/
|
||||
.header {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 99%;
|
||||
}
|
||||
.section, .lnksNdirs, .header {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
font-size: 100%;
|
||||
padding-top: 1em;
|
||||
margin-top: 1em;
|
||||
background: rgba(19, 21, 21, .7);
|
||||
color: rgba(0, 232, 255, .69); /*#00E8FF*/
|
||||
/* border-style: solid;
|
||||
border-width: .2em;
|
||||
*/
|
||||
}
|
||||
.backbutton {
|
||||
top: 3.55em;
|
||||
clear: both;
|
||||
z-index: 2;
|
||||
position: fixed;
|
||||
float: left;
|
||||
width: 5%;
|
||||
height: 50px;
|
||||
opacity: .8;
|
||||
}
|
||||
iframe {
|
||||
clear: both;
|
||||
z-index: 1;
|
||||
position: fixed;
|
||||
bottom: 5%;
|
||||
width: 40%;
|
||||
height: 500px;
|
||||
background: rgba(0,0,0,.5)
|
||||
transition: 0s;
|
||||
transition-delay:2s;
|
||||
}
|
||||
iframe:hover {
|
||||
width: 99%;
|
||||
transition-delay:2s;
|
||||
}
|
||||
.lnkStyl {
|
||||
clear: right;
|
||||
float: left;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.imgsStyl {
|
||||
margin-left: 50%;
|
||||
opacity: 0.699999988079071044921875;
|
||||
width: 32.333%;
|
||||
height: 20em;
|
||||
}
|
||||
.imgsStyl:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
#bg {
|
||||
position: fixed;
|
||||
top: 0%;
|
||||
left: 0%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
}
|
||||
#bg img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
/*START OF HOMEPAGE BUTTON SETTINGS*/
|
||||
.homeSection {
|
||||
float: left;
|
||||
width: 40%;
|
||||
height: 14em;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
font-size: 100%;
|
||||
border-style: solid;
|
||||
border-width: .2em;
|
||||
color: rgba(0, 232, 255, .69); /*#00E8FF*/
|
||||
background: rgba(19, 21, 21, .6);
|
||||
}
|
||||
/*END OF HOMEPAGE BUTTON SETTINGS*/
|
||||
|
||||
.lnkStyl {
|
||||
clear: right;
|
||||
float: left;
|
||||
border-style: solid;
|
||||
}
|
||||
.imgsStyl {
|
||||
clear: both;
|
||||
float: left;
|
||||
opacity: 0.699999988079071044921875;
|
||||
width: 20%;
|
||||
height: 15em;
|
||||
}
|
||||
.imgsStyl:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.header {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 100%;
|
||||
float: left;
|
||||
}
|
||||
.lnksNdirs {
|
||||
width: 20%;
|
||||
float:left;
|
||||
}
|
||||
/* used to format sections of pgs*/
|
||||
.section, .lnksNdirs, .header {
|
||||
clear: both;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
padding-top: 1em;
|
||||
margin-top: 1em;
|
||||
font-size: 100%;
|
||||
background: rgba(19, 21, 21, .6);
|
||||
background: rgba(19, 21, 21, .7);
|
||||
color: rgba(0, 232, 255, .69); /*#00E8FF*/
|
||||
/* border-style: solid;
|
||||
border-width: .2em;
|
||||
*/
|
||||
}
|
||||
.backbutton {
|
||||
top: 3em;
|
||||
clear: both;
|
||||
z-index: 2;
|
||||
position: fixed;
|
||||
float: left;
|
||||
width: 5%;
|
||||
height: 55px;
|
||||
opacity: .8;
|
||||
}
|
||||
.ifrmbutton2 {
|
||||
top: 3em;
|
||||
float: right;
|
||||
right: .5em;
|
||||
z-index:2;
|
||||
position: fixed;
|
||||
width: 5%;
|
||||
height: 30px;
|
||||
opacity: .6;
|
||||
}
|
||||
iframe {
|
||||
margin-left: 2%;
|
||||
z-index: 2;
|
||||
position: fixed;
|
||||
top: 8em;
|
||||
right: 0.534em;
|
||||
bottom: 2%;
|
||||
width: 75%;
|
||||
height: 525px;
|
||||
background: rgba(0,0,0,.5)
|
||||
transition: 0s;
|
||||
transition-delay:2s;
|
||||
}
|
||||
iframe:hover {
|
||||
height: 775px;
|
||||
transition-delay:2s;
|
||||
}
|
||||
#bg {
|
||||
position: fixed;
|
||||
top: 0%;
|
||||
left: 0%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
}
|
||||
#bg img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
Place Holder....
|
|
@ -1,94 +0,0 @@
|
|||
# Python imports
|
||||
import os
|
||||
import builtins
|
||||
import threading
|
||||
import re
|
||||
import secrets
|
||||
|
||||
# Lib imports
|
||||
from flask import session
|
||||
|
||||
# Application imports
|
||||
from core import app
|
||||
from core.utils import Logger
|
||||
|
||||
|
||||
|
||||
# NOTE: Threads WILL NOT die with parent's destruction.
|
||||
def threaded_wrapper(fn):
|
||||
def wrapper(*args, **kwargs):
|
||||
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start()
|
||||
return wrapper
|
||||
|
||||
# NOTE: Threads WILL die with parent's destruction.
|
||||
def daemon_threaded_wrapper(fn):
|
||||
def wrapper(*args, **kwargs):
|
||||
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
|
||||
return wrapper
|
||||
|
||||
def sizeof_fmt_def(num, suffix="B"):
|
||||
for unit in ["", "K", "M", "G", "T", "Pi", "Ei", "Zi"]:
|
||||
if abs(num) < 1024.0:
|
||||
return f"{num:3.1f} {unit}{suffix}"
|
||||
num /= 1024.0
|
||||
return f"{num:.1f} Yi{suffix}"
|
||||
|
||||
|
||||
def _get_file_size(file):
|
||||
return "4K" if isdir(file) else sizeof_fmt_def(os.path.getsize(file))
|
||||
|
||||
|
||||
|
||||
# NOTE: Just reminding myself we can add to builtins two different ways...
|
||||
# __builtins__.update({"event_system": Builtins()})
|
||||
builtins.app_name = "WebFM"
|
||||
builtins.threaded = threaded_wrapper
|
||||
builtins.daemon_threaded = daemon_threaded_wrapper
|
||||
builtins.sizeof_fmt = sizeof_fmt_def
|
||||
builtins.get_file_size = _get_file_size
|
||||
builtins.ROOT_FILE_PTH = os.path.dirname(os.path.realpath(__file__))
|
||||
builtins.BG_IMGS_PATH = ROOT_FILE_PTH + "/static/imgs/backgrounds/"
|
||||
builtins.BG_FILE_TYPE = (".webm", ".mp4", ".gif", ".jpg", ".png", ".webp")
|
||||
builtins.valid_fname_pat = re.compile(r"[a-z0-9A-Z-_\[\]\(\)\| ]{4,20}")
|
||||
builtins.logger = Logger().get_logger()
|
||||
|
||||
|
||||
|
||||
# NOTE: Need threads defined befor instantiating
|
||||
from core.utils.shellfm.windows.controller import WindowController # Get file manager controller
|
||||
window_controllers = {}
|
||||
def _get_view():
|
||||
controller = None
|
||||
try:
|
||||
controller = window_controllers[ session["win_controller_id"] ].get_window_by_index(0).get_tab_by_index(0)
|
||||
except Exception as e:
|
||||
id = secrets.token_hex(16)
|
||||
controller = WindowController()
|
||||
view = controller.create_window().create_tab()
|
||||
|
||||
try:
|
||||
view.ABS_THUMBS_PTH = app.config['ABS_THUMBS_PTH']
|
||||
except Exception as e:
|
||||
print("No ABS_THUMBS_PTH set by WebFM...")
|
||||
|
||||
try:
|
||||
view.REMUX_FOLDER = app.config['REMUX_FOLDER']
|
||||
except Exception as e:
|
||||
print("No REMUX_FOLDER set by WebFM...")
|
||||
|
||||
try:
|
||||
view.FFMPG_THUMBNLR = app.config['FFMPG_THUMBNLR']
|
||||
except Exception as e:
|
||||
print("No FFMPG_THUMBNLR set by WebFM...")
|
||||
|
||||
view.logger = logger
|
||||
|
||||
session['win_controller_id'] = id
|
||||
window_controllers.update( {id: controller } )
|
||||
controller = window_controllers[ session["win_controller_id"] ].get_window_by_index(0).get_tab_by_index(0)
|
||||
|
||||
return controller
|
||||
|
||||
|
||||
|
||||
builtins.get_view = _get_view
|
|
@ -1,45 +0,0 @@
|
|||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
from flask import Flask
|
||||
#OIDC Login path
|
||||
from flask_oidc import OpenIDConnect
|
||||
# Flask Login Path
|
||||
from flask_bcrypt import Bcrypt
|
||||
from flask_login import current_user, login_user, logout_user, LoginManager
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_object("core.config.ProductionConfig")
|
||||
# app.config.from_object("core.config.DevelopmentConfig")
|
||||
|
||||
# Apoplication imports
|
||||
from .__builtins__ import *
|
||||
|
||||
|
||||
|
||||
oidc = OpenIDConnect(app)
|
||||
login_manager = LoginManager(app)
|
||||
bcrypt = Bcrypt(app)
|
||||
|
||||
def oidc_loggedin():
|
||||
return oidc.user_loggedin
|
||||
|
||||
def oidc_isAdmin():
|
||||
if oidc_loggedin():
|
||||
isAdmin = oidc.user_getfield("isAdmin")
|
||||
if isAdmin == "yes" :
|
||||
return True
|
||||
return False
|
||||
|
||||
app.jinja_env.globals['oidc_loggedin'] = oidc_loggedin
|
||||
app.jinja_env.globals['oidc_isAdmin'] = oidc_isAdmin
|
||||
app.jinja_env.globals['TITLE'] = app.config["TITLE"]
|
||||
|
||||
|
||||
from core.models import db, User, Favorites
|
||||
db.init_app(app)
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
||||
from core.forms import RegisterForm, LoginForm
|
||||
from core import routes
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"web": {
|
||||
"auth_uri": "https://www.ssoapps.com/auth/realms/apps/protocol/openid-connect/auth",
|
||||
"client_id": "apps",
|
||||
"issuer": "https://www.ssoapps.com/auth/realms/apps",
|
||||
"client_secret": "9028c2ac-d6e0-4d96-86bd-02624b91695d",
|
||||
"redirect_uris": [
|
||||
"https%3A%2F%2Fwww.webfm.com%2F"
|
||||
],
|
||||
"userinfo_uri": "https://www.ssoapps.com/auth/realms/apps/protocol/openid-connect/userinfo",
|
||||
"token_uri": "https://www.ssoapps.com/auth/realms/apps/protocol/openid-connect/token",
|
||||
"token_introspection_uri": "https://www.ssoapps.com/auth/realms/apps/protocol/openid-connect/token/introspect"
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
# System import
|
||||
import os, secrets
|
||||
from datetime import timedelta
|
||||
|
||||
|
||||
# Lib imports
|
||||
|
||||
|
||||
# Apoplication imports
|
||||
|
||||
|
||||
# Configs
|
||||
APP_NAME = 'WebFM'
|
||||
ROOT_FILE_PTH = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
|
||||
class Config(object):
|
||||
TITLE = APP_NAME
|
||||
DEBUG = False
|
||||
TESTING = False
|
||||
THREADED = True
|
||||
SECRET_KEY = "2A#GQafbREoblgMSQYomZSxbaPE6dt#"
|
||||
# SECRET_KEY = secrets.token_hex(32)
|
||||
|
||||
PERMANENT_SESSION_LIFETIME = timedelta(days = 7).total_seconds()
|
||||
SQLALCHEMY_DATABASE_URI = "sqlite:///static/db/webfm.db"
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
|
||||
LOGIN_PATH = "OIDC" # Value can be OIDC or FLASK_LOGIN
|
||||
OIDC_TOKEN_TYPE_HINT = 'access_token'
|
||||
APP_REDIRECT_URI = "https%3A%2F%2Fwww.webfm.com%2F" # This path is submitted as the redirect URI in certain code flows
|
||||
OIDC_CLIENT_SECRETS = f'{ROOT_FILE_PTH}/client_secrets.json'
|
||||
OIDC_ID_TOKEN_COOKIE_SECURE = True
|
||||
OIDC_REQUIRE_VERIFIED_EMAIL = False
|
||||
OIDC_USER_INFO_ENABLED = True
|
||||
OIDC_VALID_ISSUERS = [
|
||||
'http://www.ssoapps.com/auth/realms/apps',
|
||||
'https://www.ssoapps.com/auth/realms/apps'
|
||||
]
|
||||
|
||||
STATIC_FPTH = f"{ROOT_FILE_PTH}/static"
|
||||
REL_THUMBS_PTH = "static/imgs/thumbnails" # Used for flask thumbnail return
|
||||
|
||||
# We are overiding some of the the shellmen view settings with these to make it all work with flask.
|
||||
# These are passed along to the shellmen view from the Routes file upon the window controller creation.
|
||||
ABS_THUMBS_PTH = f"{STATIC_FPTH}/imgs/thumbnails" # Used for thumbnail generation
|
||||
REMUX_FOLDER = f"{STATIC_FPTH}/remuxs" # Remuxed files folder
|
||||
FFMPG_THUMBNLR = f"{STATIC_FPTH}/ffmpegthumbnailer" # Thumbnail generator binary
|
||||
|
||||
|
||||
|
||||
|
||||
class ProductionConfig(Config):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class DevelopmentConfig(Config):
|
||||
DEBUG = True
|
||||
USE_RELOADER = True
|
||||
OIDC_ID_TOKEN_COOKIE_SECURE = False
|
||||
OIDC_REQUIRE_VERIFIED_EMAIL = False
|
||||
|
||||
|
||||
class TestingConfig(Config):
|
||||
TESTING = True
|
|
@ -1,24 +0,0 @@
|
|||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, PasswordField, SubmitField
|
||||
from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError
|
||||
from core import User
|
||||
|
||||
|
||||
class RegisterForm(FlaskForm):
|
||||
username = StringField('Username', validators=[DataRequired(), Length(min=4, max=24)])
|
||||
email = StringField('Email', validators=[DataRequired(), Email()])
|
||||
password = PasswordField('Password', validators=[DataRequired(), Length(min=8)])
|
||||
confirm_password = PasswordField('Confirm Password',
|
||||
validators=[DataRequired(), EqualTo('password', message="Passwords must match!")])
|
||||
submit = SubmitField("Sign Up")
|
||||
|
||||
def validate_username(self, username):
|
||||
user = User.query.filter_by(username=username.data).first()
|
||||
if user:
|
||||
raise ValidationError("User exists already! Please use a different name!")
|
||||
|
||||
|
||||
class LoginForm(FlaskForm):
|
||||
username = StringField('Username', validators=[DataRequired(), Length(min=4, max=24)])
|
||||
password = PasswordField('Password', validators=[DataRequired(), Length(min=8, max=32)])
|
||||
submit = SubmitField("Login")
|
|
@ -1,41 +0,0 @@
|
|||
# System imports
|
||||
|
||||
# Lib imports
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
# App imports
|
||||
from . import app, login_manager
|
||||
from flask_login import UserMixin
|
||||
|
||||
|
||||
db = SQLAlchemy(app)
|
||||
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
return User.query.get(int(user_id))
|
||||
|
||||
|
||||
class User(db.Model, UserMixin):
|
||||
username = db.Column(db.String, unique=True, nullable=False)
|
||||
email = db.Column(db.String, nullable=False)
|
||||
password = db.Column(db.String, nullable=False)
|
||||
id = db.Column(db.Integer, primary_key=True, unique=True, autoincrement=True)
|
||||
|
||||
def __repr__(self):
|
||||
return f"['{self.username}', '{self.email}', '{self.password}', '{self.id}']"
|
||||
|
||||
class Favorites(db.Model):
|
||||
link = db.Column(db.String, nullable=False, unique=True)
|
||||
id = db.Column(db.Integer, nullable=False, primary_key=True, unique=True, autoincrement=True)
|
||||
|
||||
def __repr__(self):
|
||||
return f"['{self.link}', '{self.id}']"
|
||||
|
||||
class Settings(db.Model):
|
||||
key = db.Column(db.String, nullable=False)
|
||||
value = db.Column(db.String, nullable=False)
|
||||
id = db.Column(db.Integer, nullable=False, primary_key=True, unique=True, autoincrement=True)
|
||||
|
||||
def __repr__(self):
|
||||
return f"['{self.key}', '{self.value}', '{self.id}']"
|
|
@ -1,119 +0,0 @@
|
|||
# Python imports
|
||||
import os
|
||||
import re
|
||||
|
||||
# Lib imports
|
||||
from flask import request
|
||||
|
||||
from flask_uploads import ALL
|
||||
from flask_uploads import configure_uploads
|
||||
from flask_uploads import UploadSet
|
||||
|
||||
|
||||
# App imports
|
||||
# Get from __init__
|
||||
from core import app
|
||||
from core import db
|
||||
from core import Favorites
|
||||
from core import oidc
|
||||
|
||||
from core.utils import MessageHandler # Get simple message processor
|
||||
|
||||
json_message = MessageHandler()
|
||||
|
||||
|
||||
|
||||
@app.route('/api/delete/<_hash>', methods=['GET', 'POST'])
|
||||
def delete_item(_hash = None):
|
||||
if request.method == 'POST':
|
||||
msg = "Log in with an Admin privlidged user to delete files!"
|
||||
if not oidc.user_loggedin:
|
||||
return json_message.create("danger", msg)
|
||||
elif oidc.user_loggedin:
|
||||
isAdmin = oidc.user_getfield("isAdmin")
|
||||
if isAdmin != "yes" :
|
||||
return json_message.create("danger", msg)
|
||||
|
||||
view = get_view()
|
||||
folder = view.get_current_directory()
|
||||
file = view.get_path_part_from_hash(_hash)
|
||||
fpath = os.path.join(folder, file)
|
||||
try:
|
||||
msg = f"[Success] Deleted the file/folder -->: {file} !"
|
||||
view.delete_file(fpath)
|
||||
return json_message.create("success", msg)
|
||||
except Exception as e:
|
||||
msg = "[Error] Unable to delete the file/folder...."
|
||||
return json_message.create("danger", msg)
|
||||
|
||||
|
||||
|
||||
@app.route('/api/create/<_type>', methods=['GET', 'POST'])
|
||||
def create_item(_type = None):
|
||||
if request.method == 'POST':
|
||||
msg = "Log in with an Admin privlidged user to upload files!"
|
||||
if not oidc.user_loggedin:
|
||||
return json_message.create("danger", msg)
|
||||
elif oidc.user_loggedin:
|
||||
isAdmin = oidc.user_getfield("isAdmin")
|
||||
if isAdmin != "yes" :
|
||||
return json_message.create("danger", msg)
|
||||
|
||||
TYPE = _type.strip()
|
||||
if not TYPE in ["dir", "file"]:
|
||||
msg = "Couldn't handle action type for api create..."
|
||||
return json_message.create("danger", msg)
|
||||
|
||||
FNAME = str(request.values['fname']).strip()
|
||||
if not re.fullmatch(valid_fname_pat, FNAME):
|
||||
msg = "A new item name can only contain alphanumeric, -, _, |, [], (), or spaces and must be minimum of 4 and max of 20 characters..."
|
||||
return json_message.create("danger", msg)
|
||||
|
||||
try:
|
||||
view = get_view()
|
||||
folder = view.get_current_directory()
|
||||
new_item = f"{folder}/{FNAME}"
|
||||
view.create_file(new_item, TYPE)
|
||||
except Exception as e:
|
||||
print(repr(e))
|
||||
msg = "Couldn't create file/folder. An unexpected error occured..."
|
||||
return json_message.create("danger", msg)
|
||||
|
||||
|
||||
msg = "[Success] created the file/dir..."
|
||||
return json_message.create("success", msg)
|
||||
else:
|
||||
msg = "Can't manage the request type..."
|
||||
return json_message.create("danger", msg)
|
||||
|
||||
|
||||
@app.route('/api/upload', methods=['GET', 'POST'])
|
||||
def upload():
|
||||
if request.method == 'POST' and len(request.files) > 0:
|
||||
msg = "Log in with an Admin privlidged user to upload files!"
|
||||
if not oidc.user_loggedin:
|
||||
return json_message.create("danger", msg)
|
||||
elif oidc.user_loggedin:
|
||||
isAdmin = oidc.user_getfield("isAdmin")
|
||||
if isAdmin != "yes" :
|
||||
return json_message.create("danger", msg)
|
||||
|
||||
view = get_view()
|
||||
folder = view.get_current_directory()
|
||||
UPLOADS_PTH = f'{folder}/'
|
||||
files = UploadSet('files', ALL, default_dest=lambda x: UPLOADS_PTH)
|
||||
configure_uploads(app, files)
|
||||
|
||||
try:
|
||||
for file in request.files:
|
||||
files.save(request.files[file])
|
||||
except Exception as e:
|
||||
print(repr(e))
|
||||
msg = "[Error] Failed to upload some or all of the file(s)..."
|
||||
return json_message.create("danger", msg)
|
||||
|
||||
msg = "[Success] Uploaded file(s)..."
|
||||
return json_message.create("success", msg)
|
||||
else:
|
||||
msg = "Can't manage the request type..."
|
||||
return json_message.create("danger", msg)
|
|
@ -1,71 +0,0 @@
|
|||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
from flask import request
|
||||
|
||||
# App imports
|
||||
from core import app
|
||||
from core import db
|
||||
from core import Favorites # Get from __init__
|
||||
from core.utils import MessageHandler # Get simple message processor
|
||||
|
||||
|
||||
json_message = MessageHandler()
|
||||
|
||||
|
||||
|
||||
@app.route('/api/list-favorites', methods=['GET', 'POST'])
|
||||
def listFavorites():
|
||||
if request.method == 'POST':
|
||||
list = db.session.query(Favorites).all()
|
||||
faves = []
|
||||
for fave in list:
|
||||
faves.append([fave.link, fave.id])
|
||||
|
||||
return json_message.faves_list(faves)
|
||||
else:
|
||||
msg = "Can't manage the request type..."
|
||||
return json_message.create("danger", msg)
|
||||
|
||||
@app.route('/api/load-favorite/<_id>', methods=['GET', 'POST'])
|
||||
def loadFavorite(_id):
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
ID = int(_id)
|
||||
fave = db.session.query(Favorites).filter_by(id = ID).first()
|
||||
view = get_view()
|
||||
view.set_path_with_sub_path(fave.link)
|
||||
return '{"refresh": "true"}'
|
||||
except Exception as e:
|
||||
print(repr(e))
|
||||
msg = "Incorrect Favorites ID..."
|
||||
return json_message.create("danger", msg)
|
||||
else:
|
||||
msg = "Can't manage the request type..."
|
||||
return json_message.create("danger", msg)
|
||||
|
||||
|
||||
@app.route('/api/manage-favorites/<_action>', methods=['GET', 'POST'])
|
||||
def manageFavorites(_action):
|
||||
if request.method == 'POST':
|
||||
ACTION = _action.strip()
|
||||
view = get_view()
|
||||
sub_path = view.get_current_sub_path()
|
||||
|
||||
if ACTION == "add":
|
||||
fave = Favorites(link = sub_path)
|
||||
db.session.add(fave)
|
||||
msg = "Added to Favorites successfully..."
|
||||
elif ACTION == "delete":
|
||||
fave = db.session.query(Favorites).filter_by(link = sub_path).first()
|
||||
db.session.delete(fave)
|
||||
msg = "Deleted from Favorites successfully..."
|
||||
else:
|
||||
msg = "Couldn't handle action for favorites item..."
|
||||
return json_message.create("danger", msg)
|
||||
|
||||
db.session.commit()
|
||||
return json_message.create("success", msg)
|
||||
else:
|
||||
msg = "Can't manage the request type..."
|
||||
return json_message.create("danger", msg)
|
|
@ -1,92 +0,0 @@
|
|||
# Python imports
|
||||
import os
|
||||
import requests
|
||||
import shutil
|
||||
|
||||
# Lib imports
|
||||
from flask import request
|
||||
|
||||
|
||||
# App imports
|
||||
# Get from __init__
|
||||
from core import app
|
||||
|
||||
from core.utils import MessageHandler # Get simple message processor
|
||||
from core.utils.tmdbscraper import scraper # Get media art scraper
|
||||
|
||||
|
||||
json_message = MessageHandler()
|
||||
tmdb = scraper.get_tmdb_scraper()
|
||||
|
||||
|
||||
@app.route('/api/get-background-poster-trailer', methods=['GET', 'POST'])
|
||||
def getPosterTrailer():
|
||||
if request.method == 'GET':
|
||||
info = {}
|
||||
view = get_view()
|
||||
dot_dots = view.get_dot_dots()
|
||||
|
||||
sub_path = view.get_current_sub_path()
|
||||
file = sub_path.split("/")[-1]
|
||||
trailer = None
|
||||
if "(" in file and ")" in file:
|
||||
title = file.split("(")[0].strip()
|
||||
startIndex = file.index('(') + 1
|
||||
endIndex = file.index(')')
|
||||
date = file[startIndex:endIndex]
|
||||
|
||||
try:
|
||||
video_data = tmdb.search(title, date)[0]
|
||||
video_id = video_data["id"]
|
||||
background_url = video_data["backdrop_path"]
|
||||
background_pth = f"{view.get_current_directory()}/000.jpg"
|
||||
|
||||
tmdb_videos = tmdb.tmdbapi.get_movie(str(video_id), append_to_response="videos")["videos"]["results"]
|
||||
for tmdb_video in tmdb_videos:
|
||||
if "YouTube" in tmdb_video["site"]:
|
||||
trailer_key = tmdb_video["key"]
|
||||
trailer = f"https://www.youtube-nocookie.com/embed/{trailer_key}?start=0&autoplay=1";
|
||||
|
||||
if not trailer:
|
||||
raise Exception("No key found. Defering to none...")
|
||||
except Exception as e:
|
||||
print("No trailer found...")
|
||||
trailer = None
|
||||
|
||||
if not os.path.isfile(background_pth):
|
||||
r = requests.get(background_url, stream = True)
|
||||
|
||||
if r.status_code == 200:
|
||||
r.raw.decode_content = True
|
||||
with open(background_pth,'wb') as f:
|
||||
shutil.copyfileobj(r.raw, f)
|
||||
|
||||
view.load_directory()
|
||||
print('Cover Background Image sucessfully retreived...')
|
||||
else:
|
||||
print('Cover Background Image Couldn\'t be retreived...')
|
||||
|
||||
info.update({'trailer': trailer})
|
||||
info.update({'poster': background_url})
|
||||
|
||||
return info
|
||||
|
||||
|
||||
@app.route('/backgrounds', methods=['GET', 'POST'])
|
||||
def backgrounds():
|
||||
files = []
|
||||
data = os.listdir(BG_IMGS_PATH)
|
||||
for file in data:
|
||||
if file.lower().endswith(BG_FILE_TYPE):
|
||||
files.append(file)
|
||||
|
||||
return json_message.backgrounds(files)
|
||||
|
||||
@app.route('/api/get-thumbnails', methods=['GET', 'POST'])
|
||||
def getThumbnails():
|
||||
if request.method == 'GET':
|
||||
view = get_view()
|
||||
return json_message.thumbnails( view.get_video_icons() )
|
||||
else:
|
||||
msg = "Can't manage the request type..."
|
||||
return json_message.create("danger", msg)
|
|
@ -1,125 +0,0 @@
|
|||
# Python imports
|
||||
import os
|
||||
|
||||
# Lib imports
|
||||
from flask import redirect
|
||||
from flask import request
|
||||
from flask import render_template
|
||||
from flask import send_from_directory
|
||||
|
||||
# App imports
|
||||
# Get from __init__
|
||||
from core import app
|
||||
from core import db
|
||||
from core import Favorites
|
||||
from core import oidc
|
||||
|
||||
from core.utils import MessageHandler # Get simple message processor
|
||||
|
||||
|
||||
|
||||
json_message = MessageHandler()
|
||||
|
||||
|
||||
|
||||
@app.route('/', methods=['GET', 'POST'])
|
||||
def home():
|
||||
if request.method == 'GET':
|
||||
view = get_view()
|
||||
_dot_dots = view.get_dot_dots()
|
||||
_current_directory = view.get_current_directory()
|
||||
return render_template('pages/index.html', current_directory = _current_directory, dot_dots = _dot_dots)
|
||||
|
||||
return render_template('error.html', title = 'Error!',
|
||||
message = 'Must use GET request type...')
|
||||
|
||||
|
||||
|
||||
@app.route('/api/list-files/<_hash>', methods=['GET', 'POST'])
|
||||
def listFiles(_hash = None):
|
||||
if request.method == 'POST':
|
||||
view = get_view()
|
||||
dot_dots = view.get_dot_dots()
|
||||
|
||||
if dot_dots[0][1] == _hash: # Refresh
|
||||
view.load_directory()
|
||||
elif dot_dots[1][1] == _hash: # Pop from dir
|
||||
view.pop_from_path()
|
||||
|
||||
msg = "Log in with an Admin privlidged user to view the requested path!"
|
||||
is_locked = view.is_folder_locked(_hash)
|
||||
if is_locked and not oidc.user_loggedin:
|
||||
return json_message.create("danger", msg)
|
||||
elif is_locked and oidc.user_loggedin:
|
||||
isAdmin = oidc.user_getfield("isAdmin")
|
||||
if isAdmin != "yes" :
|
||||
return json_message.create("danger", msg)
|
||||
|
||||
if dot_dots[0][1] != _hash and dot_dots[1][1] != _hash:
|
||||
path = view.get_path_part_from_hash(_hash)
|
||||
view.push_to_path(path)
|
||||
|
||||
error_msg = view.get_error_message()
|
||||
if error_msg:
|
||||
view.unset_error_message()
|
||||
return json_message.create("danger", error_msg)
|
||||
|
||||
sub_path = view.get_current_sub_path()
|
||||
files = view.get_files_formatted()
|
||||
fave = db.session.query(Favorites).filter_by(link = sub_path).first()
|
||||
in_fave = "true" if fave else "false"
|
||||
files.update({'in_fave': in_fave})
|
||||
return files
|
||||
else:
|
||||
msg = "Can't manage the request type..."
|
||||
return json_message.create("danger", msg)
|
||||
|
||||
|
||||
|
||||
@app.route('/api/file-manager-action/<_type>/<_hash>', methods=['GET', 'POST'])
|
||||
def fileManagerAction(_type, _hash = None):
|
||||
view = get_view()
|
||||
|
||||
if _type == "reset-path" and _hash == "None":
|
||||
view.set_to_home()
|
||||
msg = "Returning to home directory..."
|
||||
return json_message.create("success", msg)
|
||||
|
||||
folder = view.get_current_directory()
|
||||
file = view.get_path_part_from_hash(_hash)
|
||||
fpath = os.path.join(folder, file)
|
||||
logger.debug(fpath)
|
||||
|
||||
if _type == "files":
|
||||
logger.debug(f"Downloading:\n\tDirectory: {folder}\n\tFile: {file}")
|
||||
return send_from_directory(directory=folder, filename=file)
|
||||
if _type == "remux":
|
||||
# NOTE: Need to actually implimint a websocket to communicate back to client that remux has completed.
|
||||
# As is, the remux thread hangs until completion and client tries waiting until server reaches connection timeout.
|
||||
# I.E....this is stupid but for now works better than nothing
|
||||
good_result = view.remux_video(_hash, fpath)
|
||||
if good_result:
|
||||
return '{"path":"static/remuxs/' + _hash + '.mp4"}'
|
||||
else:
|
||||
msg = "Remuxing: Remux failed or took too long; please, refresh the page and try again..."
|
||||
return json_message.create("success", msg)
|
||||
|
||||
if _type == "remux":
|
||||
stream_target = view.remux_video(_hash, fpath)
|
||||
|
||||
|
||||
# NOTE: Positionally protecting actions further down that are privlidged
|
||||
# Be aware of ordering!
|
||||
msg = "Log in with an Admin privlidged user to do this action!"
|
||||
if not oidc.user_loggedin:
|
||||
return json_message.create("danger", msg)
|
||||
elif oidc.user_loggedin:
|
||||
isAdmin = oidc.user_getfield("isAdmin")
|
||||
if isAdmin != "yes" :
|
||||
return json_message.create("danger", msg)
|
||||
|
||||
|
||||
if _type == "run-locally":
|
||||
msg = "Opened media..."
|
||||
view.open_file_locally(fpath)
|
||||
return json_message.create("success", msg)
|
|
@ -1,9 +0,0 @@
|
|||
from . import Images
|
||||
from . import CRUD
|
||||
from . import Routes
|
||||
from . import Favorites
|
||||
from .pages import Flask_Login
|
||||
from .pages import Flask_Register
|
||||
from .pages import OIDC_Login
|
||||
from .pages import OIDC_Register
|
||||
from .pages import LoginManager
|
|
@ -1,37 +0,0 @@
|
|||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
from flask import request, render_template, flash, redirect, url_for
|
||||
from flask_login import current_user, login_user, logout_user
|
||||
|
||||
# App imports
|
||||
from core import app, bcrypt, db, User, LoginForm
|
||||
from core.utils import MessageHandler # Get simple message processor
|
||||
|
||||
|
||||
msgHandler = MessageHandler()
|
||||
|
||||
@app.route('/app-login', methods=['GET', 'POST'])
|
||||
def app_login():
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for("home"))
|
||||
|
||||
_form = LoginForm()
|
||||
if _form.validate_on_submit():
|
||||
user = db.session.query(User).filter(User.username == _form.username.data).first()
|
||||
|
||||
if user and bcrypt.check_password_hash(user.password, _form.password.data):
|
||||
login_user(user, remember=False)
|
||||
flash("Logged in successfully!", "success")
|
||||
return redirect(url_for("home"))
|
||||
|
||||
flash("Username or password incorrect! Please try again...", "danger")
|
||||
|
||||
return render_template('pages/login.html', form = _form)
|
||||
|
||||
|
||||
@app.route('/app-logout')
|
||||
def app_logout():
|
||||
logout_user()
|
||||
flash("Logged out successfully!", "success")
|
||||
return redirect(url_for("home"))
|
|
@ -1,30 +0,0 @@
|
|||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
from flask import request, render_template, url_for, redirect, flash
|
||||
|
||||
# App imports
|
||||
from core import app, bcrypt, db, current_user, RegisterForm # Get from __init__
|
||||
from core.models import User
|
||||
from core.utils import MessageHandler # Get simple message processor
|
||||
|
||||
|
||||
msgHandler = MessageHandler()
|
||||
|
||||
|
||||
@app.route('/app-register', methods=['GET', 'POST'])
|
||||
def app_register():
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for("home"))
|
||||
|
||||
_form = RegisterForm()
|
||||
if _form.validate_on_submit():
|
||||
hashed_password = bcrypt.generate_password_hash(_form.password.data).decode("utf-8")
|
||||
user = User(username = _form.username.data, email = _form.email.data, password = hashed_password)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
flash("Account created successfully!", "success")
|
||||
return redirect(url_for("login"))
|
||||
|
||||
return render_template('pages/register.html',
|
||||
form = _form)
|
|
@ -1,43 +0,0 @@
|
|||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
from flask import redirect, url_for, flash
|
||||
|
||||
# App imports
|
||||
from core import app
|
||||
|
||||
|
||||
ROUTE = app.config['LOGIN_PATH']
|
||||
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if ROUTE == "OIDC":
|
||||
return redirect(url_for("oidc_login"))
|
||||
if ROUTE == "FLASK_LOGIN":
|
||||
return redirect(url_for("app_login"))
|
||||
|
||||
flash("No Login Path Accessable! Please contact an Administrator!", "danger")
|
||||
return redirect(url_for("home"))
|
||||
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
if ROUTE == "OIDC":
|
||||
return redirect(url_for("oidc_logout"))
|
||||
if ROUTE == "FLASK_LOGIN":
|
||||
return redirect(url_for("app_logout"))
|
||||
|
||||
flash("No Logout Path Accessable! Please contact an Administrator!", "danger")
|
||||
return redirect(url_for("home"))
|
||||
|
||||
|
||||
@app.route('/register', methods=['GET', 'POST'])
|
||||
def register():
|
||||
if ROUTE == "OIDC":
|
||||
return redirect(url_for("oidc_register"))
|
||||
if ROUTE == "FLASK_LOGIN":
|
||||
return redirect(url_for("app_register"))
|
||||
|
||||
flash("No Register Path Accessable! Please contact an Administrator!", "danger")
|
||||
return redirect(url_for("home"))
|
|
@ -1,27 +0,0 @@
|
|||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
from flask import request, redirect, flash
|
||||
|
||||
|
||||
# App imports
|
||||
from ... import app, oidc
|
||||
|
||||
|
||||
@app.route('/oidc-login', methods=['GET', 'POST'])
|
||||
@oidc.require_login
|
||||
def oidc_login():
|
||||
print(request)
|
||||
return redirect("/")
|
||||
|
||||
|
||||
@app.route('/oidc-logout', methods=['GET', 'POST'])
|
||||
@oidc.require_login
|
||||
def oidc_logout():
|
||||
oidc.logout()
|
||||
flash("Logged out successfully!", "success")
|
||||
# NOTE: Need to redirect to logout on OIDC server to end session there too.
|
||||
# If not, we can hit login url again and get same token until it expires.
|
||||
return redirect( oidc.client_secrets.get('issuer')
|
||||
+ '/protocol/openid-connect/logout?redirect_uri='
|
||||
+ app.config['APP_REDIRECT_URI'])
|
|
@ -1,31 +0,0 @@
|
|||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
from flask import request, render_template, url_for, redirect, flash
|
||||
|
||||
# App imports
|
||||
from ... import app, oidc, db # Get from __init__
|
||||
from ...utils import MessageHandler # Get simple message processor
|
||||
|
||||
|
||||
msgHandler = MessageHandler()
|
||||
|
||||
|
||||
@app.route('/oidc-register', methods=['GET', 'POST'])
|
||||
def oidc_register():
|
||||
if oidc.user_loggedin:
|
||||
return redirect("/home")
|
||||
|
||||
_form = RegisterForm()
|
||||
if _form.validate_on_submit():
|
||||
# TODO: Create...
|
||||
# NOTE: Do a requests api here maybe??
|
||||
|
||||
# hashed_password = bcrypt.generate_password_hash(_form.password.data).decode("utf-8")
|
||||
# user = User(username=_form.username.data, password=hashed_password)
|
||||
# db.session.add(user)
|
||||
# db.session.commit()
|
||||
flash("Account created successfully!", "success")
|
||||
return redirect("/login")
|
||||
|
||||
return render_template('pages/register.html', form = _form)
|
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-123" viewBox="0 0 16 16">
|
||||
<path d="M2.873 11.297V4.142H1.699L0 5.379v1.137l1.64-1.18h.06v5.961h1.174Zm3.213-5.09v-.063c0-.618.44-1.169 1.196-1.169.676 0 1.174.44 1.174 1.106 0 .624-.42 1.101-.807 1.526L4.99 10.553v.744h4.78v-.99H6.643v-.069L8.41 8.252c.65-.724 1.237-1.332 1.237-2.27C9.646 4.849 8.723 4 7.308 4c-1.573 0-2.36 1.064-2.36 2.15v.057h1.138Zm6.559 1.883h.786c.823 0 1.374.481 1.379 1.179.01.707-.55 1.216-1.421 1.21-.77-.005-1.326-.419-1.379-.953h-1.095c.042 1.053.938 1.918 2.464 1.918 1.478 0 2.642-.839 2.62-2.144-.02-1.143-.922-1.651-1.551-1.714v-.063c.535-.09 1.347-.66 1.326-1.678-.026-1.053-.933-1.855-2.359-1.845-1.5.005-2.317.88-2.348 1.898h1.116c.032-.498.498-.944 1.206-.944.703 0 1.206.435 1.206 1.07.005.64-.504 1.106-1.2 1.106h-.75v.96Z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 870 B |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-activity" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M6 2a.5.5 0 0 1 .47.33L10 12.036l1.53-4.208A.5.5 0 0 1 12 7.5h3.5a.5.5 0 0 1 0 1h-3.15l-1.88 5.17a.5.5 0 0 1-.94 0L6 3.964 4.47 8.171A.5.5 0 0 1 4 8.5H.5a.5.5 0 0 1 0-1h3.15l1.88-5.17A.5.5 0 0 1 6 2Z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 367 B |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-alarm-fill" viewBox="0 0 16 16">
|
||||
<path d="M6 .5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1H9v1.07a7.001 7.001 0 0 1 3.274 12.474l.601.602a.5.5 0 0 1-.707.708l-.746-.746A6.97 6.97 0 0 1 8 16a6.97 6.97 0 0 1-3.422-.892l-.746.746a.5.5 0 0 1-.707-.708l.602-.602A7.001 7.001 0 0 1 7 2.07V1h-.5A.5.5 0 0 1 6 .5zm2.5 5a.5.5 0 0 0-1 0v3.362l-1.429 2.38a.5.5 0 1 0 .858.515l1.5-2.5A.5.5 0 0 0 8.5 9V5.5zM.86 5.387A2.5 2.5 0 1 1 4.387 1.86 8.035 8.035 0 0 0 .86 5.387zM11.613 1.86a2.5 2.5 0 1 1 3.527 3.527 8.035 8.035 0 0 0-3.527-3.527z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 626 B |
|
@ -1,4 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-alarm" viewBox="0 0 16 16">
|
||||
<path d="M8.5 5.5a.5.5 0 0 0-1 0v3.362l-1.429 2.38a.5.5 0 1 0 .858.515l1.5-2.5A.5.5 0 0 0 8.5 9V5.5z"/>
|
||||
<path d="M6.5 0a.5.5 0 0 0 0 1H7v1.07a7.001 7.001 0 0 0-3.273 12.474l-.602.602a.5.5 0 0 0 .707.708l.746-.746A6.97 6.97 0 0 0 8 16a6.97 6.97 0 0 0 3.422-.892l.746.746a.5.5 0 0 0 .707-.708l-.601-.602A7.001 7.001 0 0 0 9 2.07V1h.5a.5.5 0 0 0 0-1h-3zm1.038 3.018a6.093 6.093 0 0 1 .924 0 6 6 0 1 1-.924 0zM0 3.5c0 .753.333 1.429.86 1.887A8.035 8.035 0 0 1 4.387 1.86 2.5 2.5 0 0 0 0 3.5zM13.5 1c-.753 0-1.429.333-1.887.86a8.035 8.035 0 0 1 3.527 3.527A2.5 2.5 0 0 0 13.5 1z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 711 B |
|
@ -1,4 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-bottom" viewBox="0 0 16 16">
|
||||
<rect width="4" height="12" x="6" y="1" rx="1"/>
|
||||
<path d="M1.5 14a.5.5 0 0 0 0 1v-1zm13 1a.5.5 0 0 0 0-1v1zm-13 0h13v-1h-13v1z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 271 B |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-center" viewBox="0 0 16 16">
|
||||
<path d="M8 1a.5.5 0 0 1 .5.5V6h-1V1.5A.5.5 0 0 1 8 1zm0 14a.5.5 0 0 1-.5-.5V10h1v4.5a.5.5 0 0 1-.5.5zM2 7a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V7z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 315 B |
|
@ -1,4 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-end" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M14.5 1a.5.5 0 0 0-.5.5v13a.5.5 0 0 0 1 0v-13a.5.5 0 0 0-.5-.5z"/>
|
||||
<path d="M13 7a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V7z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 318 B |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-middle" viewBox="0 0 16 16">
|
||||
<path d="M6 13a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1v10zM1 8a.5.5 0 0 0 .5.5H6v-1H1.5A.5.5 0 0 0 1 8zm14 0a.5.5 0 0 1-.5.5H10v-1h4.5a.5.5 0 0 1 .5.5z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 316 B |
|
@ -1,4 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-start" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M1.5 1a.5.5 0 0 1 .5.5v13a.5.5 0 0 1-1 0v-13a.5.5 0 0 1 .5-.5z"/>
|
||||
<path d="M3 7a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V7z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 318 B |
|
@ -1,4 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-top" viewBox="0 0 16 16">
|
||||
<rect width="4" height="12" rx="1" transform="matrix(1 0 0 -1 6 15)"/>
|
||||
<path d="M1.5 2a.5.5 0 0 1 0-1v1zm13-1a.5.5 0 0 1 0 1V1zm-13 0h13v1h-13V1z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 287 B |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-alt" viewBox="0 0 16 16">
|
||||
<path d="M1 13.5a.5.5 0 0 0 .5.5h3.797a.5.5 0 0 0 .439-.26L11 3h3.5a.5.5 0 0 0 0-1h-3.797a.5.5 0 0 0-.439.26L5 13H1.5a.5.5 0 0 0-.5.5zm10 0a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 0-1h-3a.5.5 0 0 0-.5.5z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 326 B |
|
@ -1,4 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-app-indicator" viewBox="0 0 16 16">
|
||||
<path d="M5.5 2A3.5 3.5 0 0 0 2 5.5v5A3.5 3.5 0 0 0 5.5 14h5a3.5 3.5 0 0 0 3.5-3.5V8a.5.5 0 0 1 1 0v2.5a4.5 4.5 0 0 1-4.5 4.5h-5A4.5 4.5 0 0 1 1 10.5v-5A4.5 4.5 0 0 1 5.5 1H8a.5.5 0 0 1 0 1H5.5z"/>
|
||||
<path d="M16 3a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 387 B |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-app" viewBox="0 0 16 16">
|
||||
<path d="M11 2a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3h6zM5 1a4 4 0 0 0-4 4v6a4 4 0 0 0 4 4h6a4 4 0 0 0 4-4V5a4 4 0 0 0-4-4H5z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 282 B |
|
@ -1,4 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-apple" viewBox="0 0 16 16">
|
||||
<path d="M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516.024.034 1.52.087 2.475-1.258.955-1.345.762-2.391.728-2.43zm3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422.212-2.189 1.675-2.789 1.698-2.854.023-.065-.597-.79-1.254-1.157a3.692 3.692 0 0 0-1.563-.434c-.108-.003-.483-.095-1.254.116-.508.139-1.653.589-1.968.607-.316.018-1.256-.522-2.267-.665-.647-.125-1.333.131-1.824.328-.49.196-1.422.754-2.074 2.237-.652 1.482-.311 3.83-.067 4.56.244.729.625 1.924 1.273 2.796.576.984 1.34 1.667 1.659 1.899.319.232 1.219.386 1.843.067.502-.308 1.408-.485 1.766-.472.357.013 1.061.154 1.782.539.571.197 1.111.115 1.652-.105.541-.221 1.324-1.059 2.238-2.758.347-.79.505-1.217.473-1.282z"/>
|
||||
<path d="M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516.024.034 1.52.087 2.475-1.258.955-1.345.762-2.391.728-2.43zm3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422.212-2.189 1.675-2.789 1.698-2.854.023-.065-.597-.79-1.254-1.157a3.692 3.692 0 0 0-1.563-.434c-.108-.003-.483-.095-1.254.116-.508.139-1.653.589-1.968.607-.316.018-1.256-.522-2.267-.665-.647-.125-1.333.131-1.824.328-.49.196-1.422.754-2.074 2.237-.652 1.482-.311 3.83-.067 4.56.244.729.625 1.924 1.273 2.796.576.984 1.34 1.667 1.659 1.899.319.232 1.219.386 1.843.067.502-.308 1.408-.485 1.766-.472.357.013 1.061.154 1.782.539.571.197 1.111.115 1.652-.105.541-.221 1.324-1.059 2.238-2.758.347-.79.505-1.217.473-1.282z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.5 KiB |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-archive-fill" viewBox="0 0 16 16">
|
||||
<path d="M12.643 15C13.979 15 15 13.845 15 12.5V5H1v7.5C1 13.845 2.021 15 3.357 15h9.286zM5.5 7h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1 0-1zM.8 1a.8.8 0 0 0-.8.8V3a.8.8 0 0 0 .8.8h14.4A.8.8 0 0 0 16 3V1.8a.8.8 0 0 0-.8-.8H.8z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 359 B |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-archive" viewBox="0 0 16 16">
|
||||
<path d="M0 2a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1v7.5a2.5 2.5 0 0 1-2.5 2.5h-9A2.5 2.5 0 0 1 1 12.5V5a1 1 0 0 1-1-1V2zm2 3v7.5A1.5 1.5 0 0 0 3.5 14h9a1.5 1.5 0 0 0 1.5-1.5V5H2zm13-3H1v2h14V2zM5 7.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 401 B |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-90deg-down" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M4.854 14.854a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 .708-.708L4 13.293V3.5A2.5 2.5 0 0 1 6.5 1h8a.5.5 0 0 1 0 1h-8A1.5 1.5 0 0 0 5 3.5v9.793l3.146-3.147a.5.5 0 0 1 .708.708l-4 4z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 350 B |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-90deg-left" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M1.146 4.854a.5.5 0 0 1 0-.708l4-4a.5.5 0 1 1 .708.708L2.707 4H12.5A2.5 2.5 0 0 1 15 6.5v8a.5.5 0 0 1-1 0v-8A1.5 1.5 0 0 0 12.5 5H2.707l3.147 3.146a.5.5 0 1 1-.708.708l-4-4z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 349 B |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-90deg-right" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M14.854 4.854a.5.5 0 0 0 0-.708l-4-4a.5.5 0 0 0-.708.708L13.293 4H3.5A2.5 2.5 0 0 0 1 6.5v8a.5.5 0 0 0 1 0v-8A1.5 1.5 0 0 1 3.5 5h9.793l-3.147 3.146a.5.5 0 0 0 .708.708l4-4z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 350 B |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-90deg-up" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M4.854 1.146a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L4 2.707V12.5A2.5 2.5 0 0 0 6.5 15h8a.5.5 0 0 0 0-1h-8A1.5 1.5 0 0 1 5 12.5V2.707l3.146 3.147a.5.5 0 1 0 .708-.708l-4-4z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 349 B |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-bar-down" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M1 3.5a.5.5 0 0 1 .5-.5h13a.5.5 0 0 1 0 1h-13a.5.5 0 0 1-.5-.5zM8 6a.5.5 0 0 1 .5.5v5.793l2.146-2.147a.5.5 0 0 1 .708.708l-3 3a.5.5 0 0 1-.708 0l-3-3a.5.5 0 0 1 .708-.708L7.5 12.293V6.5A.5.5 0 0 1 8 6z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 375 B |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-bar-left" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M12.5 15a.5.5 0 0 1-.5-.5v-13a.5.5 0 0 1 1 0v13a.5.5 0 0 1-.5.5zM10 8a.5.5 0 0 1-.5.5H3.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L3.707 7.5H9.5a.5.5 0 0 1 .5.5z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 375 B |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-bar-right" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M6 8a.5.5 0 0 0 .5.5h5.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3a.5.5 0 0 0 0-.708l-3-3a.5.5 0 0 0-.708.708L12.293 7.5H6.5A.5.5 0 0 0 6 8zm-2.5 7a.5.5 0 0 1-.5-.5v-13a.5.5 0 0 1 1 0v13a.5.5 0 0 1-.5.5z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 375 B |