iobroker.staticsfilefolder 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +41 -0
- package/admin/index_m.html +130 -0
- package/admin/staticsfilefolder.png +0 -0
- package/admin/style.css +32 -0
- package/admin/words.js +46 -0
- package/io-package.json +96 -0
- package/lib/adapter-config.d.ts +19 -0
- package/lib/web.js +76 -0
- package/main.js +138 -0
- package/package.json +74 -0
- package/www/app.js +321 -0
- package/www/index.html +72 -0
- package/www/libs/mammoth.browser.min.js +20 -0
- package/www/libs/pdfjs/pdf.min.mjs +29 -0
- package/www/libs/pdfjs/pdf.worker.min.mjs +29 -0
- package/www/libs/xlsx.full.min.js +22 -0
- package/www/style.css +265 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 gokturk413 <gokturk413@gmail.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+

|
|
2
|
+
# ioBroker.staticsfilefolder
|
|
3
|
+
|
|
4
|
+
[](https://www.npmjs.com/package/iobroker.staticsfilefolder)
|
|
5
|
+
[](https://www.npmjs.com/package/iobroker.staticsfilefolder)
|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Static Files Folder Explorer for ioBroker
|
|
10
|
+
An ioBroker adapter extension that provides a beautifully designed, modern Single Page Application (SPA) to browse, view, and interact with your static document files directly in the browser—completely offline!
|
|
11
|
+
|
|
12
|
+
This adapter acts as a web extension for the `ioBroker.web` adapter. It scans a specified directory on your server (e.g. `reports/`) and serves the files to a stunning web interface.
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
* **Modern SPA Interface:** A fast and responsive Single Page Application with dynamic routing, sorting, and filtering.
|
|
16
|
+
* **Offline Document Viewing:** Built-in offline support for viewing standard documents directly in the browser without relying on external CDNs or internet access:
|
|
17
|
+
* **PDFs** (via `pdf.js`)
|
|
18
|
+
* **Excel Files** (via `xlsx`)
|
|
19
|
+
* **Word Documents** (via `mammoth`)
|
|
20
|
+
* **Live File Monitoring (Chokidar):** The adapter monitors your reports directory in real-time. When a new file is added (e.g. by another archive tool), ioBroker states `staticsfilefolder.0.latest_file_url` and `staticsfilefolder.0.latest_file_path` are immediately updated.
|
|
21
|
+
* **Smart Highlighting:** Files generated "Today" are distinctly highlighted with a vibrant design to stand out from historical archives.
|
|
22
|
+
* **Navigation:** Native "Forward" and "Back" navigation through folders.
|
|
23
|
+
|
|
24
|
+
### Usage
|
|
25
|
+
1. Configure your target directory path in the adapter settings.
|
|
26
|
+
2. Ensure the `ioBroker.web` adapter is installed and running.
|
|
27
|
+
3. Access your files via `http://<iobroker-ip>:8082/staticsfilefolder/` (assuming your web adapter runs on port 8082).
|
|
28
|
+
|
|
29
|
+
### Requirements
|
|
30
|
+
* Node.js 18.x or higher
|
|
31
|
+
* ioBroker JS-Controller >= 3.3.22
|
|
32
|
+
* ioBroker Web Adapter >= 4.0.0
|
|
33
|
+
|
|
34
|
+
### Changelog
|
|
35
|
+
#### 0.0.1
|
|
36
|
+
* (gokturk413) initial release: SPA, Offline document viewers, Chokidar file observer.
|
|
37
|
+
|
|
38
|
+
### License
|
|
39
|
+
MIT License
|
|
40
|
+
|
|
41
|
+
Copyright (c) 2026 gokturk413 <gokturk413@gmail.com>
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
<html>
|
|
2
|
+
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<!-- Load ioBroker scripts and styles-->
|
|
6
|
+
<link rel="stylesheet" type="text/css" href="../../css/adapter.css" />
|
|
7
|
+
<link rel="stylesheet" type="text/css" href="../../lib/css/materialize.css">
|
|
8
|
+
|
|
9
|
+
<script type="text/javascript" src="../../lib/js/jquery-3.2.1.min.js"></script>
|
|
10
|
+
<script type="text/javascript" src="../../socket.io/socket.io.js"></script>
|
|
11
|
+
|
|
12
|
+
<script type="text/javascript" src="../../js/translate.js"></script>
|
|
13
|
+
<script type="text/javascript" src="../../lib/js/materialize.js"></script>
|
|
14
|
+
<script type="text/javascript" src="../../js/adapter-settings.js"></script>
|
|
15
|
+
|
|
16
|
+
<!-- Load our own files -->
|
|
17
|
+
<link rel="stylesheet" type="text/css" href="style.css" />
|
|
18
|
+
<script type="text/javascript" src="words.js"></script>
|
|
19
|
+
|
|
20
|
+
<script type="text/javascript">
|
|
21
|
+
// This will be called by the admin adapter when the settings page loads
|
|
22
|
+
function load(settings, onChange) {
|
|
23
|
+
// example: select elements with id=key and class=value and insert value
|
|
24
|
+
if (!settings) return;
|
|
25
|
+
$('.value').each(function () {
|
|
26
|
+
var $key = $(this);
|
|
27
|
+
var id = $key.attr('id');
|
|
28
|
+
if ($key.attr('type') === 'checkbox') {
|
|
29
|
+
// do not call onChange direct, because onChange could expect some arguments
|
|
30
|
+
$key.prop('checked', settings[id])
|
|
31
|
+
.on('change', () => onChange())
|
|
32
|
+
;
|
|
33
|
+
} else {
|
|
34
|
+
// do not call onChange direct, because onChange could expect some arguments
|
|
35
|
+
$key.val(settings[id])
|
|
36
|
+
.on('change', () => onChange())
|
|
37
|
+
.on('keyup', () => onChange())
|
|
38
|
+
;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
getExtendableInstances(function (result) {
|
|
43
|
+
if (result) {
|
|
44
|
+
var text = '';
|
|
45
|
+
for (var r = 0; r < result.length; r++) {
|
|
46
|
+
var name = result[r]._id.substring('system.adapter.'.length);
|
|
47
|
+
text += '<option value="' + name + '" ' + (settings.webInstance === name ? 'selected' : '') + '>' + name + '</option>';
|
|
48
|
+
}
|
|
49
|
+
$('#webInstance').append(text).select();
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
onChange(false);
|
|
53
|
+
// reinitialize all the Materialize labels on the page if you are dynamically adding inputs:
|
|
54
|
+
if (M) M.updateTextFields();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// This will be called by the admin adapter when the user presses the save button
|
|
58
|
+
function save(callback) {
|
|
59
|
+
// example: select elements with class=value and build settings object
|
|
60
|
+
var obj = {};
|
|
61
|
+
$('.value').each(function () {
|
|
62
|
+
var $this = $(this);
|
|
63
|
+
if ($this.attr('type') === 'checkbox') {
|
|
64
|
+
obj[$this.attr('id')] = $this.prop('checked');
|
|
65
|
+
} else if ($this.attr('type') === 'number') {
|
|
66
|
+
obj[$this.attr('id')] = parseFloat($this.val());
|
|
67
|
+
} else {
|
|
68
|
+
obj[$this.attr('id')] = $this.val();
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
callback(obj);
|
|
72
|
+
}
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
</head>
|
|
76
|
+
|
|
77
|
+
<body>
|
|
78
|
+
|
|
79
|
+
<div class="adapter-container m">
|
|
80
|
+
<div class="row">
|
|
81
|
+
<div class="col s12">
|
|
82
|
+
<ul class="tabs">
|
|
83
|
+
<li class="tab col s4"><a href="#tab-main" class="translate active">Main settings</a></li>
|
|
84
|
+
</ul>
|
|
85
|
+
</div>
|
|
86
|
+
<div id="tab-main" class="col s12 page">
|
|
87
|
+
<div class="row">
|
|
88
|
+
<div class="col s12 m4 l2">
|
|
89
|
+
<img src="staticsfilefolder.png" class="logo">
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
<div class="row">
|
|
93
|
+
<div class="col s12 m4">
|
|
94
|
+
<select class="value" id="webInstance">
|
|
95
|
+
<option value="*" class="translate">all</option>
|
|
96
|
+
</select>
|
|
97
|
+
<label for="webInstance" class="translate">Extend WEB adapter:</label>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
<div class="row">
|
|
101
|
+
<div class="col s12 m4">
|
|
102
|
+
<input class="value" id="route"/>
|
|
103
|
+
<label for="route" class="translate">Route path:</label>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
<div class="row">
|
|
107
|
+
<div class="col s12 m4">
|
|
108
|
+
<input class="value" id="dirname"/>
|
|
109
|
+
<label for="dirname" class="translate">Static Files Directory:</label>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
<!--<div class="row">
|
|
113
|
+
<div class="col s12 m4 input-field">
|
|
114
|
+
<input class="value" id="simpleProxy" type="checkbox"/>
|
|
115
|
+
<span for="route" class="translate">Use simple proxy (no sockets)</span>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
<div class="row">
|
|
119
|
+
<div class="col s12 m4">
|
|
120
|
+
<input class="value" id="errorTimeout" type="number" min="1000"/>
|
|
121
|
+
<label for="errorTimeout" class="translate">Error timeout(ms):</label>
|
|
122
|
+
</div>
|
|
123
|
+
</div>-->
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
</body>
|
|
129
|
+
|
|
130
|
+
</html>
|
|
Binary file
|
package/admin/style.css
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/* You can delete those if you want. I just found them very helpful */
|
|
2
|
+
* {
|
|
3
|
+
box-sizing: border-box
|
|
4
|
+
}
|
|
5
|
+
.m {
|
|
6
|
+
/* Don't cut off dropdowns! */
|
|
7
|
+
overflow: initial;
|
|
8
|
+
}
|
|
9
|
+
.m.adapter-container,
|
|
10
|
+
.m.adapter-container > div.App {
|
|
11
|
+
/* Fix layout/scrolling issues with tabs */
|
|
12
|
+
height: 100%;
|
|
13
|
+
width: 100%;
|
|
14
|
+
position: relative;
|
|
15
|
+
}
|
|
16
|
+
.m .select-wrapper + label {
|
|
17
|
+
/* The positioning for dropdown labels is messed up */
|
|
18
|
+
transform: none !important;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
label > i[title] {
|
|
22
|
+
/* Display the help cursor for the tooltip icons and fix their positioning */
|
|
23
|
+
cursor: help;
|
|
24
|
+
margin-left: 0.25em;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.dropdown-content {
|
|
28
|
+
/* Don't wrap text in dropdowns */
|
|
29
|
+
white-space: nowrap;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Add your styles here */
|
package/admin/words.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/* eslint no-unused-vars: off */
|
|
2
|
+
/* eslint no-global-assign: off */
|
|
3
|
+
/* global systemDictionary */
|
|
4
|
+
"use strict";
|
|
5
|
+
|
|
6
|
+
systemDictionary = {
|
|
7
|
+
"staticsfilefolder adapter settings": {
|
|
8
|
+
"en": "Adapter settings for staticsfilefolder",
|
|
9
|
+
"de": "Adaptereinstellungen für staticsfilefolder",
|
|
10
|
+
"ru": "Настройки адаптера для staticsfilefolder",
|
|
11
|
+
"pt": "Configurações do adaptador para staticsfilefolder",
|
|
12
|
+
"nl": "Adapterinstellingen voor staticsfilefolder",
|
|
13
|
+
"fr": "Paramètres d'adaptateur pour staticsfilefolder",
|
|
14
|
+
"it": "Impostazioni dell'adattatore per staticsfilefolder",
|
|
15
|
+
"es": "Ajustes del adaptador para staticsfilefolder",
|
|
16
|
+
"pl": "Ustawienia adaptera dla staticsfilefolder",
|
|
17
|
+
"uk": "Налаштування адаптера для staticsfilefolder",
|
|
18
|
+
"zh-cn": "staticsfilefolder的适配器设置"
|
|
19
|
+
},
|
|
20
|
+
"option1": {
|
|
21
|
+
"en": "option1",
|
|
22
|
+
"de": "Option 1",
|
|
23
|
+
"ru": "Опция 1",
|
|
24
|
+
"pt": "Opção 1",
|
|
25
|
+
"nl": "Optie 1",
|
|
26
|
+
"fr": "Option 1",
|
|
27
|
+
"it": "opzione 1",
|
|
28
|
+
"es": "Opción 1",
|
|
29
|
+
"pl": "opcja 1",
|
|
30
|
+
"uk": "варіант1",
|
|
31
|
+
"zh-cn": "选项1"
|
|
32
|
+
},
|
|
33
|
+
"option2": {
|
|
34
|
+
"en": "option2",
|
|
35
|
+
"de": "Option 2",
|
|
36
|
+
"ru": "вариант 2",
|
|
37
|
+
"pt": "opção 2",
|
|
38
|
+
"nl": "Optie 2",
|
|
39
|
+
"fr": "Option 2",
|
|
40
|
+
"it": "opzione 2",
|
|
41
|
+
"es": "opcion 2",
|
|
42
|
+
"pl": "Opcja 2",
|
|
43
|
+
"uk": "варіант2",
|
|
44
|
+
"zh-cn": "选项2"
|
|
45
|
+
}
|
|
46
|
+
};
|
package/io-package.json
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"common": {
|
|
3
|
+
"name": "staticsfilefolder",
|
|
4
|
+
"version": "0.0.2",
|
|
5
|
+
"news": {
|
|
6
|
+
"0.0.2": {
|
|
7
|
+
"en": "Added UI Theme support, fixed Node.js version compatibility issues and updated tests."
|
|
8
|
+
},
|
|
9
|
+
"0.0.1": {
|
|
10
|
+
"en": "initial release",
|
|
11
|
+
"de": "Erstveröffentlichung",
|
|
12
|
+
"ru": "Начальная версия",
|
|
13
|
+
"pt": "lançamento inicial",
|
|
14
|
+
"nl": "Eerste uitgave",
|
|
15
|
+
"fr": "Première version",
|
|
16
|
+
"it": "Versione iniziale",
|
|
17
|
+
"es": "Versión inicial",
|
|
18
|
+
"pl": "Pierwsze wydanie",
|
|
19
|
+
"uk": "Початкова версія",
|
|
20
|
+
"zh-cn": "首次出版"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"title": "Static Files Folder",
|
|
24
|
+
"titleLang": {
|
|
25
|
+
"en": "Static Files Folder",
|
|
26
|
+
"de": "Statischer Dateiordner",
|
|
27
|
+
"ru": "Папка статических файлов",
|
|
28
|
+
"pt": "Pasta de arquivos estáticos",
|
|
29
|
+
"nl": "Statische bestandenmap",
|
|
30
|
+
"fr": "Dossier de fichiers statiques",
|
|
31
|
+
"it": "Cartella file statici",
|
|
32
|
+
"es": "Carpeta de archivos estáticos",
|
|
33
|
+
"pl": "Folder plików statycznych",
|
|
34
|
+
"uk": "Папка статичних файлів",
|
|
35
|
+
"zh-cn": "静态文件夹"
|
|
36
|
+
},
|
|
37
|
+
"desc": {
|
|
38
|
+
"en": "Static Files Folder",
|
|
39
|
+
"de": "Statischer Dateiordner",
|
|
40
|
+
"ru": "Папка статических файлов",
|
|
41
|
+
"pt": "Pasta de arquivos estáticos",
|
|
42
|
+
"nl": "Statische bestandenmap",
|
|
43
|
+
"fr": "Dossier de fichiers statiques",
|
|
44
|
+
"it": "Cartella file statici",
|
|
45
|
+
"es": "Carpeta de archivos estáticos",
|
|
46
|
+
"pl": "Folder plików statycznych",
|
|
47
|
+
"uk": "Папка статичних файлів",
|
|
48
|
+
"zh-cn": "静态文件夹"
|
|
49
|
+
},
|
|
50
|
+
"authors": [
|
|
51
|
+
"gokturk413 <gokturk413@gmail.com>"
|
|
52
|
+
],
|
|
53
|
+
"keywords": [
|
|
54
|
+
"Static Files Directory Browsing"
|
|
55
|
+
],
|
|
56
|
+
"licenseInformation": {
|
|
57
|
+
"type": "free",
|
|
58
|
+
"license": "MIT"
|
|
59
|
+
},
|
|
60
|
+
"platform": "Javascript/Node.js",
|
|
61
|
+
"main": "main.js",
|
|
62
|
+
"icon": "staticsfilefolder.png",
|
|
63
|
+
"enabled": true,
|
|
64
|
+
"extIcon": "https://raw.githubusercontent.com/gokturk413/ioBroker.staticsfilefolder/main/admin/staticsfilefolder.png",
|
|
65
|
+
"readme": "https://github.com/gokturk413/ioBroker.staticsfilefolder/blob/main/README.md",
|
|
66
|
+
"loglevel": "info",
|
|
67
|
+
"tier": 3,
|
|
68
|
+
"mode": "extension",
|
|
69
|
+
"type": "utility",
|
|
70
|
+
"compact": true,
|
|
71
|
+
"webExtension": "lib/web.js",
|
|
72
|
+
"connectionType": "local",
|
|
73
|
+
"dataSource": "poll",
|
|
74
|
+
"adminUI": {
|
|
75
|
+
"config": "materialize"
|
|
76
|
+
},
|
|
77
|
+
"dependencies": [
|
|
78
|
+
{
|
|
79
|
+
"js-controller": ">=3.3.22",
|
|
80
|
+
"web": ">=4.0.0"
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
"globalDependencies": [
|
|
84
|
+
{
|
|
85
|
+
"admin": ">=5.0.0"
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
},
|
|
89
|
+
"native": {
|
|
90
|
+
"option1": true,
|
|
91
|
+
"option2": "42",
|
|
92
|
+
"webInstance": "*"
|
|
93
|
+
},
|
|
94
|
+
"objects": [],
|
|
95
|
+
"instanceObjects": []
|
|
96
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// This file extends the AdapterConfig type from "@types/iobroker"
|
|
2
|
+
// using the actual properties present in io-package.json
|
|
3
|
+
// in order to provide typings for adapter.config properties
|
|
4
|
+
|
|
5
|
+
import { native } from "../io-package.json";
|
|
6
|
+
|
|
7
|
+
type _AdapterConfig = typeof native;
|
|
8
|
+
|
|
9
|
+
// Augment the globally declared type ioBroker.AdapterConfig
|
|
10
|
+
declare global {
|
|
11
|
+
namespace ioBroker {
|
|
12
|
+
interface AdapterConfig extends _AdapterConfig {
|
|
13
|
+
// Do not enter anything here!
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// this is required so the above AdapterConfig is found by TypeScript / type checking
|
|
19
|
+
export {};
|
package/lib/web.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
|
|
5
|
+
class ExtensionExample {
|
|
6
|
+
/**
|
|
7
|
+
* @param {object} server http or https node.js object
|
|
8
|
+
* @param {object} webSettings settings of the web server
|
|
9
|
+
* @param {object} adapter web adapter object
|
|
10
|
+
* @param {object} instanceSettings instance object with common and native
|
|
11
|
+
* @param {object} app express application
|
|
12
|
+
*/
|
|
13
|
+
constructor(server, webSettings, adapter, instanceSettings, app) {
|
|
14
|
+
this.app = app;
|
|
15
|
+
this.config = instanceSettings ? instanceSettings.native : {};
|
|
16
|
+
this.config.route = this.config.route || "demo";
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
adapter.log.info("Install extension on /" + this.config.route);
|
|
20
|
+
|
|
21
|
+
// Serve our SPA static files from www folder
|
|
22
|
+
const wwwPath = path.join(__dirname, "../www");
|
|
23
|
+
this.app.use("/" + this.config.route, express.static(wwwPath));
|
|
24
|
+
|
|
25
|
+
// Serve the reports directory so files can be accessed via URL
|
|
26
|
+
if (this.config.dirname) {
|
|
27
|
+
this.app.use("/" + this.config.route + "/files", express.static(this.config.dirname));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// API Endpoint for listing files
|
|
31
|
+
this.app.get("/" + this.config.route + "/api/list", (req, res) => {
|
|
32
|
+
try {
|
|
33
|
+
const relPath = req.query.path || "";
|
|
34
|
+
if (relPath.includes("..")) {
|
|
35
|
+
return res.status(400).json({error: "Invalid path"});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const targetDir = path.join(this.config.dirname, relPath);
|
|
39
|
+
|
|
40
|
+
if (!fs.existsSync(targetDir) || !fs.statSync(targetDir).isDirectory()) {
|
|
41
|
+
return res.status(404).json({error: "Directory not found"});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const items = fs.readdirSync(targetDir);
|
|
45
|
+
const result = [];
|
|
46
|
+
for (const item of items) {
|
|
47
|
+
try {
|
|
48
|
+
const itemPath = path.join(targetDir, item);
|
|
49
|
+
const stat = fs.statSync(itemPath);
|
|
50
|
+
result.push({
|
|
51
|
+
name: item,
|
|
52
|
+
isDirectory: stat.isDirectory(),
|
|
53
|
+
size: stat.size,
|
|
54
|
+
mtime: stat.mtimeMs,
|
|
55
|
+
birthtime: stat.birthtimeMs,
|
|
56
|
+
ext: path.extname(item).toLowerCase()
|
|
57
|
+
});
|
|
58
|
+
} catch (err) {
|
|
59
|
+
// ignore errors
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
res.json({ success: true, items: result, path: relPath });
|
|
64
|
+
} catch(e) {
|
|
65
|
+
adapter.log.error("Error in /api/list: " + e);
|
|
66
|
+
res.status(500).json({error: e.toString()});
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
} catch (err) {
|
|
71
|
+
adapter.log.error("Error during Extension init: " + err.stack);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = ExtensionExample;
|
package/main.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Created with @iobroker/create-adapter v2.6.2
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// The adapter-core module gives you access to the core ioBroker functions
|
|
8
|
+
// you need to create an adapter
|
|
9
|
+
const utils = require("@iobroker/adapter-core");
|
|
10
|
+
|
|
11
|
+
// Load your modules here, e.g.:
|
|
12
|
+
// const fs = require("fs");
|
|
13
|
+
|
|
14
|
+
class Staticsfilefolder extends utils.Adapter {
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {Partial<utils.AdapterOptions>} [options={}]
|
|
18
|
+
*/
|
|
19
|
+
constructor(options) {
|
|
20
|
+
super({
|
|
21
|
+
...options,
|
|
22
|
+
name: "staticsfilefolder",
|
|
23
|
+
});
|
|
24
|
+
this.on("ready", this.onReady.bind(this));
|
|
25
|
+
this.on("stateChange", this.onStateChange.bind(this));
|
|
26
|
+
this.on("unload", this.onUnload.bind(this));
|
|
27
|
+
|
|
28
|
+
this.watcher = null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Is called when databases are connected and adapter received configuration.
|
|
33
|
+
*/
|
|
34
|
+
async onReady() {
|
|
35
|
+
// Create state for latest file url
|
|
36
|
+
await this.setObjectNotExistsAsync("latest_file_url", {
|
|
37
|
+
type: "state",
|
|
38
|
+
common: {
|
|
39
|
+
name: "Latest File URL",
|
|
40
|
+
type: "string",
|
|
41
|
+
role: "url",
|
|
42
|
+
read: true,
|
|
43
|
+
write: false,
|
|
44
|
+
},
|
|
45
|
+
native: {},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Create state for the latest file path
|
|
49
|
+
await this.setObjectNotExistsAsync("latest_file_path", {
|
|
50
|
+
type: "state",
|
|
51
|
+
common: {
|
|
52
|
+
name: "Latest File Path",
|
|
53
|
+
type: "string",
|
|
54
|
+
role: "value",
|
|
55
|
+
read: true,
|
|
56
|
+
write: false,
|
|
57
|
+
},
|
|
58
|
+
native: {},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (this.config.dirname) {
|
|
62
|
+
this.log.info("Starting file observer on: " + this.config.dirname);
|
|
63
|
+
try {
|
|
64
|
+
const chokidar = require("chokidar");
|
|
65
|
+
const path = require("path");
|
|
66
|
+
|
|
67
|
+
this.watcher = chokidar.watch(this.config.dirname, {
|
|
68
|
+
ignored: /(^|[/\\])\../, // ignore dotfiles
|
|
69
|
+
persistent: true,
|
|
70
|
+
ignoreInitial: true // do not fire for existing files on startup
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
this.watcher.on("add", async (filePath) => {
|
|
74
|
+
this.log.info(`New file detected: ${filePath}`);
|
|
75
|
+
|
|
76
|
+
// Calculate relative path for URL
|
|
77
|
+
const relativePath = path.relative(this.config.dirname, filePath).replace(/\\/g, "/");
|
|
78
|
+
const route = this.config.route || "demo";
|
|
79
|
+
|
|
80
|
+
// Assuming the web adapter serves this extension under the web server's port
|
|
81
|
+
// and relative path is encoded.
|
|
82
|
+
const fileUrl = `/${route}/files/${encodeURI(relativePath)}`;
|
|
83
|
+
|
|
84
|
+
await this.setStateAsync("latest_file_url", { val: fileUrl, ack: true });
|
|
85
|
+
await this.setStateAsync("latest_file_path", { val: filePath, ack: true });
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
this.watcher.on("error", error => this.log.error(`Watcher error: ${error}`));
|
|
89
|
+
} catch (e) {
|
|
90
|
+
this.log.error("Failed to start file observer: " + e);
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
this.log.warn("Directory name not configured. File observer will not start.");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Is called when adapter shuts down - callback has to be called under any circumstances!
|
|
99
|
+
* @param {() => void} callback
|
|
100
|
+
*/
|
|
101
|
+
onUnload(callback) {
|
|
102
|
+
try {
|
|
103
|
+
if (this.watcher) {
|
|
104
|
+
this.watcher.close();
|
|
105
|
+
this.watcher = null;
|
|
106
|
+
}
|
|
107
|
+
callback();
|
|
108
|
+
} catch (e) {
|
|
109
|
+
callback();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Is called if a subscribed state changes
|
|
115
|
+
* @param {string} id
|
|
116
|
+
* @param {ioBroker.State | null | undefined} state
|
|
117
|
+
*/
|
|
118
|
+
onStateChange(id, state) {
|
|
119
|
+
if (state) {
|
|
120
|
+
// The state was changed
|
|
121
|
+
this.log.info(`state ${id} changed: ${state.val} (ack = ${state.ack})`);
|
|
122
|
+
} else {
|
|
123
|
+
// The state was deleted
|
|
124
|
+
this.log.info(`state ${id} deleted`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (require.main !== module) {
|
|
130
|
+
// Export the constructor in compact mode
|
|
131
|
+
/**
|
|
132
|
+
* @param {Partial<utils.AdapterOptions>} [options={}]
|
|
133
|
+
*/
|
|
134
|
+
module.exports = (options) => new Staticsfilefolder(options);
|
|
135
|
+
} else {
|
|
136
|
+
// otherwise start the instance directly
|
|
137
|
+
new Staticsfilefolder();
|
|
138
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "iobroker.staticsfilefolder",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Static Files Folder",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "gokturk413",
|
|
7
|
+
"email": "gokturk413@gmail.com"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/gokturk413/ioBroker.staticsfilefolder",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"Static Files Directory Browsing"
|
|
13
|
+
],
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/gokturk413/ioBroker.staticsfilefolder.git"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">= 20"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@iobroker/adapter-core": "^3.0.4",
|
|
23
|
+
"chokidar": "^5.0.0",
|
|
24
|
+
"express": "^4.18.3"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@alcalzone/release-script": "^3.7.0",
|
|
28
|
+
"@alcalzone/release-script-plugin-iobroker": "^3.7.0",
|
|
29
|
+
"@alcalzone/release-script-plugin-license": "^3.7.0",
|
|
30
|
+
"@alcalzone/release-script-plugin-manual-review": "^3.7.0",
|
|
31
|
+
"@iobroker/adapter-dev": "^1.3.0",
|
|
32
|
+
"@iobroker/testing": "^4.1.1",
|
|
33
|
+
"@tsconfig/node18": "^18.2.2",
|
|
34
|
+
"@types/chai": "^4.3.12",
|
|
35
|
+
"@types/chai-as-promised": "^7.1.8",
|
|
36
|
+
"@types/mocha": "^10.0.6",
|
|
37
|
+
"@types/node": "^18.19.24",
|
|
38
|
+
"@types/proxyquire": "^1.3.31",
|
|
39
|
+
"@types/sinon": "^17.0.3",
|
|
40
|
+
"@types/sinon-chai": "^3.2.12",
|
|
41
|
+
"chai": "^4.4.1",
|
|
42
|
+
"chai-as-promised": "^7.1.1",
|
|
43
|
+
"eslint": "^8.57.0",
|
|
44
|
+
"mocha": "^10.3.0",
|
|
45
|
+
"proxyquire": "^2.1.3",
|
|
46
|
+
"sinon": "^17.0.1",
|
|
47
|
+
"sinon-chai": "^3.7.0",
|
|
48
|
+
"typescript": "~5.0.4"
|
|
49
|
+
},
|
|
50
|
+
"main": "main.js",
|
|
51
|
+
"files": [
|
|
52
|
+
"admin{,/!(src)/**}/!(tsconfig|tsconfig.*|.eslintrc).{json,json5}",
|
|
53
|
+
"admin{,/!(src)/**}/*.{html,css,png,svg,jpg,js}",
|
|
54
|
+
"lib/",
|
|
55
|
+
"www/",
|
|
56
|
+
"io-package.json",
|
|
57
|
+
"LICENSE",
|
|
58
|
+
"main.js"
|
|
59
|
+
],
|
|
60
|
+
"scripts": {
|
|
61
|
+
"test:js": "mocha --config test/mocharc.custom.json \"{!(node_modules|test)/**/*.test.js,*.test.js,test/**/test!(PackageFiles|Startup).js}\"",
|
|
62
|
+
"test:package": "mocha test/package --exit",
|
|
63
|
+
"test:integration": "mocha test/integration --exit",
|
|
64
|
+
"test": "npm run test:js && npm run test:package",
|
|
65
|
+
"check": "tsc --noEmit -p tsconfig.check.json",
|
|
66
|
+
"lint": "eslint .",
|
|
67
|
+
"translate": "translate-adapter",
|
|
68
|
+
"release": "release-script"
|
|
69
|
+
},
|
|
70
|
+
"bugs": {
|
|
71
|
+
"url": "https://github.com/gokturk413/ioBroker.staticsfilefolder/issues"
|
|
72
|
+
},
|
|
73
|
+
"readmeFilename": "README.md"
|
|
74
|
+
}
|