Compare commits

..

No commits in common. "master" and "php-legacy-version" have entirely different histories.

2061 changed files with 2068 additions and 29600 deletions

143
.gitignore vendored
View File

@ -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/

BIN
Documents/000.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

BIN
Images/000.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

BIN
Images/pic1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
Images/pic2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

BIN
Images/pic3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

BIN
Images/pic4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

BIN
Images/pic5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

BIN
Images/pic6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

BIN
Music/000.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1019 KiB

View File

@ -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)

BIN
Videos/000.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

View File

@ -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 $@;

View File

@ -0,0 +1 @@
LOL...Not really!

View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 370 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 299 KiB

120
index.html Normal file
View File

@ -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')">&#9881;</button>
<button type="button" title="Refresh" onclick="getDir('./')">&#8635;</button>
<button type="button" title="Back" onclick="getDir('../')">&lArr;</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 &#8597;</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..." >&#9734;</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">&#8765;</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>

View File

@ -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

8
resources/css/base.css Normal file
View File

@ -0,0 +1,8 @@
html {
margin: 0em;
padding: 0em;
}
ol, ul, li {
list-style: none;
}

4
resources/css/iframe.css Normal file
View File

@ -0,0 +1,4 @@
#controls, #fullPathHeader, #dynDiv,
.errorStyling, .dirStyle, .movieStyle, .fileStyle {
background-color: rgba(0,0,0,0.2);
}

304
resources/css/main.css Normal file
View File

@ -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); }

BIN
resources/db/webfm.db Normal file

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 216 KiB

After

Width:  |  Height:  |  Size: 216 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@ -0,0 +1 @@
Place Holder File

54
resources/js/ajax.js Normal file
View 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);
}

View File

@ -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 "";
}

34
resources/js/favorites.js Normal file
View File

@ -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("./");
}

View File

@ -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);
}

167
resources/js/jsonParser.js Normal file
View File

@ -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";
}
}

View File

@ -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);
}

189
resources/js/uiActions.js Normal file
View File

@ -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 = "&#8599;";
aTag.appendChild(popOutDiv);
toLocDiv.title = "Open In Local Program";
toLocDiv.className = "popOutBttn";
toLocDiv.innerHTML = "&#8765;";
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) { }
}

155
resources/js/uiEvents.js Normal file
View File

@ -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 "";
}

15
resources/php/config.php Normal file
View File

@ -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/";
?>

View File

@ -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);
}
?>

View File

@ -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);
}
?>

View File

@ -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);
}
?>

View File

@ -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);
}
?>

View File

@ -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();
}
?>

View File

@ -0,0 +1,12 @@
<?php
function serverMessage($TYPE, $MESSAGE) {
$GeneratedJSON = array( 'message' =>
array(
'type' => $TYPE,
'text' => $MESSAGE
)
);
echo json_encode($GeneratedJSON);
}
?>

16
resources/php/sse.php Normal file
View File

@ -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();
?>

91
resources/themes/Grid List.css Executable file
View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -0,0 +1 @@
Place Holder....

View File

@ -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

View File

@ -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

View File

@ -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"
}
}

View File

@ -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

View File

@ -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")

View File

@ -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}']"

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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"))

View File

@ -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)

View File

@ -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"))

View File

@ -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'])

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

Some files were not shown because too many files have changed in this diff Show More