mirror of
https://github.com/nextcloud/server.git
synced 2026-06-09 00:32:29 -04:00
feat(files_external): Migrate settings to Vue
Template parameters are migrated to initial state, common state between admin and user settings is shared in the CommonSettingsTrait. The template is cleaned and replaced with only a stub for the Vue mount. Code only used for the frontend of the settings is moved from the MountConfig to the CommonSettingsTrait (the missing dependency messages). On the frontend a wrapper view is created that currently holds the global credentials settings and the external storages settings. - The global credentials sections is now a stand-alone sections - fully implemented. - The external storages section holds the table + user config + warnings on missing dependencies The legacy UI is temporarly renamed but will be removed in a following commit. Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
parent
d4674060dc
commit
e1133ec926
25 changed files with 2324 additions and 2143 deletions
|
|
@ -1,4 +0,0 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/#files_external{margin-bottom:0px}#externalStorage{margin:15px 0 20px 0}#externalStorage tr.externalStorageLoading>td{text-align:center}#externalStorage td{height:50px}#externalStorage td.mountOptionsToggle,#externalStorage td.remove,#externalStorage td.save{position:relative;padding:0 !important;width:44px}#externalStorage td.mountOptionsToggle [class^=icon-],#externalStorage td.mountOptionsToggle [class*=" icon-"],#externalStorage td.remove [class^=icon-],#externalStorage td.remove [class*=" icon-"],#externalStorage td.save [class^=icon-],#externalStorage td.save [class*=" icon-"]{width:44px;height:44px;margin:3px;opacity:.5;padding:14px;vertical-align:text-bottom;cursor:pointer}#externalStorage td.mountOptionsToggle [class^=icon-]:hover,#externalStorage td.mountOptionsToggle [class*=" icon-"]:hover,#externalStorage td.remove [class^=icon-]:hover,#externalStorage td.remove [class*=" icon-"]:hover,#externalStorage td.save [class^=icon-]:hover,#externalStorage td.save [class*=" icon-"]:hover{opacity:1}#externalStorage td.mountPoint,#externalStorage td.backend,#externalStorage td.authentication,#externalStorage td.configuration{min-width:160px;width:15%}#externalStorage td.status{display:table-cell;vertical-align:middle;width:43px}#externalStorage td.status>span{display:inline-block;height:28px;width:28px;vertical-align:text-bottom;border-radius:50%;cursor:pointer}#externalStorage td>input:not(.applicableToAllUsers),#externalStorage td>select{width:100%}#externalStorage td>img{padding-top:7px;opacity:.5}#externalStorage td>img:hover{cursor:pointer;opacity:1}#externalStorage .popovermenu li>.menuitem{width:fit-content !important}#addMountPoint>td{border:none}#addMountPoint>td.applicable{visibility:hidden}#addMountPoint>td.hidden{visibility:hidden}#selectBackend{margin-inline-start:-10px;width:150px}#externalStorage td.configuration,#externalStorage td.backend{white-space:normal}#externalStorage td.configuration>*{white-space:nowrap}#externalStorage td.configuration input.added{margin-inline-end:6px}#externalStorage label>input[type=checkbox]{margin-inline-end:3px}#externalStorage td.configuration label{width:100%;display:inline-flex;align-items:center}#externalStorage td.configuration input.disabled-success{background-color:rgba(134,255,110,.9)}#externalStorage td.applicable label{display:inline-flex;align-items:center}#externalStorage td.applicable div.chzn-container{position:relative;top:3px}#externalStorage .select2-container.applicableUsers{width:100% !important}#userMountingBackends{padding-inline-start:25px}.files-external-select2 .select2-results .select2-result-label{height:32px;padding:3px}.files-external-select2 .select2-results .select2-result-label>span{display:block;position:relative}.files-external-select2 .select2-results .select2-result-label .avatardiv{display:inline-block}.files-external-select2 .select2-results .select2-result-label .avatardiv+span{position:absolute;top:5px;margin-inline-start:10px}.files-external-select2 .select2-results .select2-result-label .avatardiv[data-type=group]+span{vertical-align:top;top:6px;position:absolute;max-width:80%;inset-inline-start:30px;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}#externalStorage .select2-container .select2-search-choice{display:flex}#externalStorage .select2-container .select2-search-choice .select2-search-choice-close{display:block;inset-inline-start:auto;position:relative;width:20px}#externalStorage .mountOptionsToggle .dropdown{width:auto}.nav-icon-external-storage{background-image:var(--icon-external-dark)}.global_credentials__personal{margin:10px auto;text-align:center;width:min(400px,100vw)}/*# sourceMappingURL=settings.css.map */
|
||||
|
|
@ -1 +0,0 @@
|
|||
{"version":3,"sourceRoot":"","sources":["settings.scss"],"names":[],"mappings":"AAAA;AAAA;AAAA;AAAA,GAIA,gBACC,kBAGD,iBACC,qBAEA,8CACC,kBAGD,oBACC,YAEA,2FAGC,kBACA,qBACA,WACA,yRAEC,WACA,YACA,WACA,WACA,aACA,2BACA,eACA,6TACC,UAKH,gIAIC,gBACA,UAGD,2BAEC,mBACA,sBAEA,WAEA,gCACC,qBACA,YACA,WACA,2BACA,kBACA,eAIF,gFACC,WAGD,wBACC,gBACA,WAEA,8BACC,eACA,UAKH,2CACC,6BAIF,8BAEA,+CAEA,2CAEA,eACC,0BACA,YAGD,8DAEC,mBAGD,oCACC,mBAGD,8CACC,sBAGD,4CACC,sBAGD,wCACC,WACA,oBACA,mBAGD,yDACC,sCAGD,qCACC,oBACA,mBAGD,kDACC,kBACA,QAGD,oDACC,sBAGD,sBACC,0BAGD,+DACC,YACA,YAGD,oEACC,cACA,kBAGD,0EACC,qBAGD,+EACC,kBACA,QACA,yBAGD,gGACC,mBACA,QACA,kBACA,cACA,wBACA,uBACA,mBACA,gBAGD,2DACC,aACA,wFACC,cACA,wBACA,kBACA,WAIF,+CACC,WAGD,2BACC,2CAGD,8BACI,iBACA,kBACA","file":"settings.css"}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
|
@ -1,194 +0,0 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
#files_external {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
#externalStorage {
|
||||
margin: 15px 0 20px 0;
|
||||
|
||||
tr.externalStorageLoading > td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
td {
|
||||
height: 50px;
|
||||
|
||||
&.mountOptionsToggle,
|
||||
&.remove,
|
||||
&.save {
|
||||
position: relative;
|
||||
padding: 0 !important;
|
||||
width: 44px;
|
||||
[class^='icon-'],
|
||||
[class*=' icon-'] {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
margin: 3px;
|
||||
opacity: 0.5;
|
||||
padding: 14px;
|
||||
vertical-align: text-bottom;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.mountPoint,
|
||||
&.backend,
|
||||
&.authentication,
|
||||
&.configuration {
|
||||
min-width: 160px;
|
||||
width: 15%;
|
||||
}
|
||||
|
||||
&.status {
|
||||
/* overwrite conflicting core styles */
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
/* ensure width does not change even if internal span is not displayed */
|
||||
width: 43px;
|
||||
|
||||
> span {
|
||||
display: inline-block;
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
vertical-align: text-bottom;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
> input:not(.applicableToAllUsers), & > select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
> img {
|
||||
padding-top: 7px;
|
||||
opacity: 0.5;
|
||||
|
||||
&:hover {
|
||||
cursor:pointer;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popovermenu li > .menuitem {
|
||||
width: fit-content !important;
|
||||
}
|
||||
}
|
||||
|
||||
#addMountPoint>td { border:none; }
|
||||
|
||||
#addMountPoint>td.applicable { visibility:hidden; }
|
||||
|
||||
#addMountPoint>td.hidden { visibility:hidden; }
|
||||
|
||||
#selectBackend {
|
||||
margin-inline-start: -10px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
#externalStorage td.configuration,
|
||||
#externalStorage td.backend {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
#externalStorage td.configuration > * {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#externalStorage td.configuration input.added {
|
||||
margin-inline-end: 6px;
|
||||
}
|
||||
|
||||
#externalStorage label > input[type="checkbox"] {
|
||||
margin-inline-end: 3px;
|
||||
}
|
||||
|
||||
#externalStorage td.configuration label {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#externalStorage td.configuration input.disabled-success {
|
||||
background-color: rgba(134, 255, 110, 0.9);
|
||||
}
|
||||
|
||||
#externalStorage td.applicable label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#externalStorage td.applicable div.chzn-container {
|
||||
position: relative;
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
#externalStorage .select2-container.applicableUsers {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
#userMountingBackends {
|
||||
padding-inline-start: 25px;
|
||||
}
|
||||
|
||||
.files-external-select2 .select2-results .select2-result-label {
|
||||
height: 32px;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.files-external-select2 .select2-results .select2-result-label > span {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.files-external-select2 .select2-results .select2-result-label .avatardiv {
|
||||
display:inline-block;
|
||||
}
|
||||
|
||||
.files-external-select2 .select2-results .select2-result-label .avatardiv + span {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
margin-inline-start: 10px;
|
||||
}
|
||||
|
||||
.files-external-select2 .select2-results .select2-result-label .avatardiv[data-type="group"] + span {
|
||||
vertical-align: top;
|
||||
top: 6px;
|
||||
position: absolute;
|
||||
max-width: 80%;
|
||||
inset-inline-start: 30px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#externalStorage .select2-container .select2-search-choice {
|
||||
display: flex;
|
||||
.select2-search-choice-close {
|
||||
display: block;
|
||||
inset-inline-start: auto;
|
||||
position: relative;
|
||||
width: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
#externalStorage .mountOptionsToggle .dropdown {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.nav-icon-external-storage {
|
||||
background-image: var(--icon-external-dark);
|
||||
}
|
||||
|
||||
.global_credentials__personal {
|
||||
margin: 10px auto;
|
||||
text-align: center;
|
||||
width: min(400px, 100vw);
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 -960 960 960" width="20px"><path d="M456-432h156.48q24.96 0 42.24-17.39Q672-466.77 672-491.89q0-25.11-17.42-42.61T612-552h-1q-4.83-30.72-27.99-51.36Q559.85-624 528-624q-26 0-45.98 12.96-19.99 12.96-30.46 35.04-28.54 1.92-48.05 22.56Q384-532.8 384-504q0 28.8 21 50.4 21 21.6 51 21.6ZM120-144q-29.7 0-50.85-21.15Q48-186.3 48-216v-504h72v504h633v72H120Zm144-144q-29.7 0-50.85-21.15Q192-330.3 192-360v-432q0-29.7 21.15-50.85Q234.3-864 264-864h168l96 96h264q30-1 51 20.44 21 21.45 21 51.56v336q0 29.7-21.15 50.85Q821.7-288 792-288H264Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 -960 960 960" width="20px"><path d="M456-432h156.48q24.96 0 42.24-17.39Q672-466.77 672-491.89q0-25.11-17.42-42.61T612-552h-1q-4.83-30.72-27.99-51.36Q559.85-624 528-624q-26 0-45.98 12.96-19.99 12.96-30.46 35.04-28.54 1.92-48.05 22.56Q384-532.8 384-504q0 28.8 21 50.4 21 21.6 51 21.6ZM120-144q-29.7 0-50.85-21.15Q48-186.3 48-216v-504h72v504h633v72H120Zm144-144q-29.7 0-50.85-21.15Q192-330.3 192-360v-432q0-29.7 21.15-50.85Q234.3-864 264-864h168l96 96h264q30-1 51 20.44 21 21.45 21 51.56v336q0 29.7-21.15 50.85Q821.7-288 792-288H264Z"/></svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 604 B After Width: | Height: | Size: 605 B |
1514
apps/files_external/js/legacy-settings.js
Normal file
1514
apps/files_external/js/legacy-settings.js
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,47 +0,0 @@
|
|||
(function() {
|
||||
var template = Handlebars.template, templates = OCA.Files_External.Templates = OCA.Files_External.Templates || {};
|
||||
templates['credentialsDialog'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=container.hooks.helperMissing, alias3="function", alias4=container.escapeExpression, lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||
return parent[propertyName];
|
||||
}
|
||||
return undefined
|
||||
};
|
||||
|
||||
return "<div id=\"files_external_div_form\"><div>\n <div>"
|
||||
+ alias4(((helper = (helper = lookupProperty(helpers,"credentials_text") || (depth0 != null ? lookupProperty(depth0,"credentials_text") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"credentials_text","hash":{},"data":data,"loc":{"start":{"line":2,"column":6},"end":{"line":2,"column":26}}}) : helper)))
|
||||
+ "</div>\n <form>\n <input type=\"text\" name=\"username\" placeholder=\""
|
||||
+ alias4(((helper = (helper = lookupProperty(helpers,"placeholder_username") || (depth0 != null ? lookupProperty(depth0,"placeholder_username") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"placeholder_username","hash":{},"data":data,"loc":{"start":{"line":4,"column":51},"end":{"line":4,"column":75}}}) : helper)))
|
||||
+ "\"/>\n <input type=\"password\" name=\"password\" placeholder=\""
|
||||
+ alias4(((helper = (helper = lookupProperty(helpers,"placeholder_password") || (depth0 != null ? lookupProperty(depth0,"placeholder_password") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"placeholder_password","hash":{},"data":data,"loc":{"start":{"line":5,"column":55},"end":{"line":5,"column":79}}}) : helper)))
|
||||
+ "\"/>\n </form>\n </div>\n</div>\n";
|
||||
},"useData":true});
|
||||
templates['mountOptionsDropDown'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=container.hooks.helperMissing, alias3="function", alias4=container.escapeExpression, lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||
return parent[propertyName];
|
||||
}
|
||||
return undefined
|
||||
};
|
||||
|
||||
return "<div class=\"popovermenu open\">\n <ul>\n <li class=\"optionRow\">\n <span class=\"menuitem\">\n <input id=\"mountOptionsEncrypt\" class=\"checkbox\" name=\"encrypt\" type=\"checkbox\" value=\"true\" checked=\"checked\"/>\n <label for=\"mountOptionsEncrypt\">"
|
||||
+ alias4(((helper = (helper = lookupProperty(helpers,"mountOptionsEncryptLabel") || (depth0 != null ? lookupProperty(depth0,"mountOptionsEncryptLabel") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"mountOptionsEncryptLabel","hash":{},"data":data,"loc":{"start":{"line":6,"column":37},"end":{"line":6,"column":65}}}) : helper)))
|
||||
+ "</label>\n </span>\n </li>\n <li class=\"optionRow\">\n <span class=\"menuitem\">\n <input id=\"mountOptionsPreviews\" class=\"checkbox\" name=\"previews\" type=\"checkbox\" value=\"true\" checked=\"checked\"/>\n <label for=\"mountOptionsPreviews\">"
|
||||
+ alias4(((helper = (helper = lookupProperty(helpers,"mountOptionsPreviewsLabel") || (depth0 != null ? lookupProperty(depth0,"mountOptionsPreviewsLabel") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"mountOptionsPreviewsLabel","hash":{},"data":data,"loc":{"start":{"line":12,"column":38},"end":{"line":12,"column":67}}}) : helper)))
|
||||
+ "</label>\n </span>\n </li>\n <li class=\"optionRow\">\n <span class=\"menuitem\">\n <input id=\"mountOptionsSharing\" class=\"checkbox\" name=\"enable_sharing\" type=\"checkbox\" value=\"true\"/>\n <label for=\"mountOptionsSharing\">"
|
||||
+ alias4(((helper = (helper = lookupProperty(helpers,"mountOptionsSharingLabel") || (depth0 != null ? lookupProperty(depth0,"mountOptionsSharingLabel") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"mountOptionsSharingLabel","hash":{},"data":data,"loc":{"start":{"line":18,"column":37},"end":{"line":18,"column":65}}}) : helper)))
|
||||
+ "</label>\n </span>\n </li>\n <li class=\"optionRow\">\n <span class=\"menuitem icon-search\">\n <label for=\"mountOptionsFilesystemCheck\">"
|
||||
+ alias4(((helper = (helper = lookupProperty(helpers,"mountOptionsFilesystemCheckLabel") || (depth0 != null ? lookupProperty(depth0,"mountOptionsFilesystemCheckLabel") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"mountOptionsFilesystemCheckLabel","hash":{},"data":data,"loc":{"start":{"line":23,"column":45},"end":{"line":23,"column":81}}}) : helper)))
|
||||
+ "</label>\n <select id=\"mountOptionsFilesystemCheck\" name=\"filesystem_check_changes\" data-type=\"int\">\n <option value=\"0\">"
|
||||
+ alias4(((helper = (helper = lookupProperty(helpers,"mountOptionsFilesystemCheckOnce") || (depth0 != null ? lookupProperty(depth0,"mountOptionsFilesystemCheckOnce") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"mountOptionsFilesystemCheckOnce","hash":{},"data":data,"loc":{"start":{"line":25,"column":23},"end":{"line":25,"column":58}}}) : helper)))
|
||||
+ "</option>\n <option value=\"1\" selected=\"selected\">"
|
||||
+ alias4(((helper = (helper = lookupProperty(helpers,"mountOptionsFilesystemCheckDA") || (depth0 != null ? lookupProperty(depth0,"mountOptionsFilesystemCheckDA") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"mountOptionsFilesystemCheckDA","hash":{},"data":data,"loc":{"start":{"line":26,"column":43},"end":{"line":26,"column":76}}}) : helper)))
|
||||
+ "</option>\n </select>\n </span>\n </li>\n <li class=\"optionRow\">\n <span class=\"menuitem\">\n <input id=\"mountOptionsEncoding\" class=\"checkbox\" name=\"encoding_compatibility\" type=\"checkbox\" value=\"true\"/>\n <label for=\"mountOptionsEncoding\">"
|
||||
+ alias4(((helper = (helper = lookupProperty(helpers,"mountOptionsEncodingLabel") || (depth0 != null ? lookupProperty(depth0,"mountOptionsEncodingLabel") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"mountOptionsEncodingLabel","hash":{},"data":data,"loc":{"start":{"line":33,"column":38},"end":{"line":33,"column":67}}}) : helper)))
|
||||
+ "</label>\n </span>\n </li>\n <li class=\"optionRow\">\n <span class=\"menuitem\">\n <input id=\"mountOptionsReadOnly\" class=\"checkbox\" name=\"readonly\" type=\"checkbox\" value=\"true\"/>\n <label for=\"mountOptionsReadOnly\">"
|
||||
+ alias4(((helper = (helper = lookupProperty(helpers,"mountOptionsReadOnlyLabel") || (depth0 != null ? lookupProperty(depth0,"mountOptionsReadOnlyLabel") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"mountOptionsReadOnlyLabel","hash":{},"data":data,"loc":{"start":{"line":39,"column":38},"end":{"line":39,"column":67}}}) : helper)))
|
||||
+ "</label>\n </span>\n </li>\n <li class=\"optionRow persistent\">\n <a href=\"#\" class=\"menuitem remove icon-delete\">\n <span>"
|
||||
+ alias4(((helper = (helper = lookupProperty(helpers,"deleteLabel") || (depth0 != null ? lookupProperty(depth0,"deleteLabel") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"deleteLabel","hash":{},"data":data,"loc":{"start":{"line":44,"column":10},"end":{"line":44,"column":25}}}) : helper)))
|
||||
+ "</span>\n </a>\n </li>\n </ul>\n</div>\n";
|
||||
},"useData":true});
|
||||
})();
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
<div id="files_external_div_form"><div>
|
||||
<div>{{credentials_text}}</div>
|
||||
<form>
|
||||
<input type="text" name="username" placeholder="{{placeholder_username}}"/>
|
||||
<input type="password" name="password" placeholder="{{placeholder_password}}"/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -18,10 +18,8 @@ use OCA\Files_External\Service\UserStoragesService;
|
|||
use OCP\AppFramework\QueryException;
|
||||
use OCP\Files\StorageNotAvailableException;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use OCP\Security\ISecureRandom;
|
||||
use OCP\Server;
|
||||
use OCP\Util;
|
||||
use phpseclib\Crypt\AES;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
|
|
@ -110,52 +108,6 @@ class MountConfig {
|
|||
return StorageNotAvailableException::STATUS_ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get backend dependency message
|
||||
* TODO: move into AppFramework along with templates
|
||||
*
|
||||
* @param Backend[] $backends
|
||||
*/
|
||||
public static function dependencyMessage(array $backends): string {
|
||||
$l = Util::getL10N('files_external');
|
||||
$message = '';
|
||||
$dependencyGroups = [];
|
||||
|
||||
foreach ($backends as $backend) {
|
||||
foreach ($backend->checkDependencies() as $dependency) {
|
||||
$dependencyMessage = $dependency->getMessage();
|
||||
if ($dependencyMessage !== null) {
|
||||
$message .= '<p>' . $dependencyMessage . '</p>';
|
||||
} else {
|
||||
$dependencyGroups[$dependency->getDependency()][] = $backend;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($dependencyGroups as $module => $dependants) {
|
||||
$backends = implode(', ', array_map(function (Backend $backend): string {
|
||||
return '"' . $backend->getText() . '"';
|
||||
}, $dependants));
|
||||
$message .= '<p>' . MountConfig::getSingleDependencyMessage($l, $module, $backends) . '</p>';
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a dependency missing message
|
||||
*/
|
||||
private static function getSingleDependencyMessage(IL10N $l, string $module, string $backend): string {
|
||||
switch (strtolower($module)) {
|
||||
case 'curl':
|
||||
return $l->t('The cURL support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it.', [$backend]);
|
||||
case 'ftp':
|
||||
return $l->t('The FTP support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it.', [$backend]);
|
||||
default:
|
||||
return $l->t('"%1$s" is not installed. Mounting of %2$s is not possible. Please ask your system administrator to install it.', [$module, $backend]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt passwords in the given config options
|
||||
*
|
||||
|
|
|
|||
|
|
@ -7,11 +7,13 @@
|
|||
namespace OCA\Files_External\Settings;
|
||||
|
||||
use OCA\Files_External\Lib\Auth\Password\GlobalAuth;
|
||||
use OCA\Files_External\MountConfig;
|
||||
use OCA\Files_External\Lib\Backend\Backend;
|
||||
use OCA\Files_External\Service\BackendService;
|
||||
use OCA\Files_External\Service\GlobalStoragesService;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\AppFramework\Services\IInitialState;
|
||||
use OCP\Encryption\IManager;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\Settings\ISettings;
|
||||
|
||||
class Admin implements ISettings {
|
||||
|
|
@ -22,6 +24,8 @@ class Admin implements ISettings {
|
|||
private GlobalStoragesService $globalStoragesService,
|
||||
private BackendService $backendService,
|
||||
private GlobalAuth $globalAuth,
|
||||
private IInitialState $initialState,
|
||||
private IURLGenerator $urlGenerator,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -29,20 +33,29 @@ class Admin implements ISettings {
|
|||
* @return TemplateResponse
|
||||
*/
|
||||
public function getForm() {
|
||||
$parameters = [
|
||||
'encryptionEnabled' => $this->encryptionManager->isEnabled(),
|
||||
'visibilityType' => BackendService::VISIBILITY_ADMIN,
|
||||
'storages' => $this->globalStoragesService->getStorages(),
|
||||
'backends' => $this->backendService->getAvailableBackends(),
|
||||
'authMechanisms' => $this->backendService->getAuthMechanisms(),
|
||||
'dependencies' => MountConfig::dependencyMessage($this->backendService->getBackends()),
|
||||
// Shared settings (user & admin)
|
||||
$this->setInitialState(BackendService::VISIBILITY_ADMIN);
|
||||
|
||||
// Admin specific
|
||||
$backends = $this->backendService->getAvailableBackends();
|
||||
$allowedBackends = array_filter($backends, fn (Backend $backend) => $backend->isVisibleFor(BackendService::VISIBILITY_PERSONAL));
|
||||
$this->initialState->provideInitialState('user-mounting', [
|
||||
'allowUserMounting' => $this->backendService->isUserMountingAllowed(),
|
||||
'globalCredentials' => $this->globalAuth->getAuth(''),
|
||||
'globalCredentialsUid' => '',
|
||||
];
|
||||
'allowedBackends' => array_values(array_map(fn (Backend $backend) => $backend->getIdentifier(), $allowedBackends)),
|
||||
'backends' => array_values(
|
||||
array_map(
|
||||
fn (Backend $backend) => [
|
||||
'id' => $backend->getIdentifier(),
|
||||
'displayName' => $backend->getText(),
|
||||
'deprecated' => $backend->getDeprecateTo()?->getIdentifier(),
|
||||
],
|
||||
$backends,
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
||||
$this->loadScriptsAndStyles();
|
||||
return new TemplateResponse('files_external', 'settings', $parameters, '');
|
||||
return new TemplateResponse('files_external', 'settings', renderAs: '');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,34 +3,58 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2024 Ferdinand Thiessen <opensource@fthiessen.de>
|
||||
*
|
||||
* @author Ferdinand Thiessen <opensource@fthiessen.de>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Files_External\Settings;
|
||||
|
||||
use OCA\Files_External\Lib\Auth\Password\GlobalAuth;
|
||||
use OCA\Files_External\Lib\Backend\Backend;
|
||||
use OCA\Files_External\Service\BackendService;
|
||||
use OCP\AppFramework\Services\IInitialState;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\Util;
|
||||
|
||||
trait CommonSettingsTrait {
|
||||
protected BackendService $backendService;
|
||||
private BackendService $backendService;
|
||||
|
||||
private IInitialState $initialState;
|
||||
|
||||
private IURLGenerator $urlGenerator;
|
||||
|
||||
private GlobalAuth $globalAuth;
|
||||
|
||||
private ?string $userId = null;
|
||||
|
||||
/**
|
||||
* Set the initial state for the user / admin settings
|
||||
*
|
||||
* @param int $visibilityType The visibility type used to determine which options to show (admin vs user settings)
|
||||
*/
|
||||
protected function setInitialState(int $visibilityType) {
|
||||
$allowUserMounting = $this->backendService->isUserMountingAllowed();
|
||||
$isAdmin = $visibilityType === BackendService::VISIBILITY_ADMIN;
|
||||
$canCreateMounts = $isAdmin || $allowUserMounting;
|
||||
|
||||
$this->initialState->provideInitialState('settings', [
|
||||
/** Link to external files documentation */
|
||||
'docUrl' => $this->urlGenerator->linkToDocs('admin-external-storage'),
|
||||
/** List of backend dependency or missing module issues to be shown on the fronend */
|
||||
'dependencyIssues' => $canCreateMounts ? $this->dependencyMessage() : null,
|
||||
/** Is this the admin settings or just user settings */
|
||||
'isAdmin' => $isAdmin,
|
||||
]);
|
||||
|
||||
$this->initialState->provideInitialState(
|
||||
'global-credentials',
|
||||
array_merge(
|
||||
/** User ID of the credentials - empty string for global admin defined */
|
||||
['uid' => $this->userId ?? '' ],
|
||||
/** username and password configured */
|
||||
$this->globalAuth->getAuth($this->userId ?? ''),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the frontend script including the custom backend dependencies
|
||||
|
|
@ -52,4 +76,36 @@ trait CommonSettingsTrait {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get backend dependency error messages
|
||||
* @return array{messages: string[], modules: array<string,string[]>}
|
||||
*/
|
||||
private function dependencyMessage(): array {
|
||||
$messages = [];
|
||||
$dependencyGroups = [];
|
||||
|
||||
// Try all backends and check their dependencies
|
||||
foreach ($this->backendService->getAvailableBackends() as $backend) {
|
||||
foreach ($backend->checkDependencies() as $dependency) {
|
||||
$dependencyMessage = $dependency->getMessage();
|
||||
if ($dependencyMessage !== null) {
|
||||
// There is a custom message so we use that
|
||||
$messages[] = $dependencyMessage;
|
||||
} else {
|
||||
// No custom message so just add the dependency and add the backend to the list of dependants
|
||||
$dependencyGroups[$dependency->getDependency()][] = $backend;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$backendDisplayName = fn (Backend $backend) => $backend->getText();
|
||||
|
||||
// Create a mapping [ 'dependency' => ['backendName1', ... ]]
|
||||
$missingModules = array_map(fn (array $dependants) => array_map($backendDisplayName, $dependants), $dependencyGroups);
|
||||
return [
|
||||
'messages' => $messages,
|
||||
'modules' => $missingModules,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,46 +7,32 @@
|
|||
namespace OCA\Files_External\Settings;
|
||||
|
||||
use OCA\Files_External\Lib\Auth\Password\GlobalAuth;
|
||||
use OCA\Files_External\MountConfig;
|
||||
use OCA\Files_External\Service\BackendService;
|
||||
use OCA\Files_External\Service\UserGlobalStoragesService;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\Encryption\IManager;
|
||||
use OCP\IUserSession;
|
||||
use OCP\AppFramework\Services\IInitialState;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\Settings\ISettings;
|
||||
|
||||
class Personal implements ISettings {
|
||||
use CommonSettingsTrait;
|
||||
|
||||
public function __construct(
|
||||
private IManager $encryptionManager,
|
||||
private UserGlobalStoragesService $userGlobalStoragesService,
|
||||
?string $userId,
|
||||
private BackendService $backendService,
|
||||
private GlobalAuth $globalAuth,
|
||||
private IUserSession $userSession,
|
||||
private IInitialState $initialState,
|
||||
private IURLGenerator $urlGenerator,
|
||||
) {
|
||||
$this->userId = $userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TemplateResponse
|
||||
*/
|
||||
public function getForm() {
|
||||
$uid = $this->userSession->getUser()->getUID();
|
||||
|
||||
$parameters = [
|
||||
'encryptionEnabled' => $this->encryptionManager->isEnabled(),
|
||||
'visibilityType' => BackendService::VISIBILITY_PERSONAL,
|
||||
'storages' => $this->userGlobalStoragesService->getStorages(),
|
||||
'backends' => $this->backendService->getAvailableBackends(),
|
||||
'authMechanisms' => $this->backendService->getAuthMechanisms(),
|
||||
'dependencies' => MountConfig::dependencyMessage($this->backendService->getBackends()),
|
||||
'allowUserMounting' => $this->backendService->isUserMountingAllowed(),
|
||||
'globalCredentials' => $this->globalAuth->getAuth($uid),
|
||||
'globalCredentialsUid' => $uid,
|
||||
];
|
||||
$this->setInitialState(BackendService::VISIBILITY_PERSONAL);
|
||||
$this->loadScriptsAndStyles();
|
||||
|
||||
return new TemplateResponse('files_external', 'settings', $parameters, '');
|
||||
return new TemplateResponse('files_external', 'settings', renderAs: '');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
40
apps/files_external/src/components/ExternalStorageTable.vue
Normal file
40
apps/files_external/src/components/ExternalStorageTable.vue
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<template>
|
||||
<!-- The ID is just for backwards compatibility -->
|
||||
<table id="externalStorage">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<span class="hidden-visually" v-text="t('files_external', 'Folder name')" />
|
||||
</th>
|
||||
<th>{{ t('files_external', 'Folder name') }}</th>
|
||||
<th>{{ t('files_external', 'External storage') }}</th>
|
||||
<th>{{ t('files_external', 'Authentication') }}</th>
|
||||
<th>{{ t('files_external', 'Configuration') }}</th>
|
||||
<th v-if="isAdmin">
|
||||
{{ t('files_external', 'Available for') }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<ExternalStorageRow />
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
const { isAdmin } = loadState<{ isAdmin: boolean }>('files_external', 'settings')
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ExternalStorageTable',
|
||||
|
||||
setup() {
|
||||
// Non reactive props
|
||||
return {
|
||||
isAdmin,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
120
apps/files_external/src/components/UserMountSettings.vue
Normal file
120
apps/files_external/src/components/UserMountSettings.vue
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
<script setup lang="ts">
|
||||
import { showError, showSuccess } from '@nextcloud/dialogs'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import { ref, watch } from 'vue'
|
||||
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
|
||||
|
||||
const userMounting = loadState<{
|
||||
allowUserMounting: boolean
|
||||
allowedBackends: string[]
|
||||
backends: {
|
||||
id: string
|
||||
displayName: string
|
||||
deprecated?: string
|
||||
}[]
|
||||
}>('files_external', 'user-mounting')
|
||||
|
||||
const availableBackends = userMounting.backends
|
||||
const allowUserMounting = ref(userMounting.allowUserMounting)
|
||||
const allowedBackends = ref<string[]>(userMounting.allowedBackends)
|
||||
|
||||
/**
|
||||
* When changing the enabled state of the user-mounting settings then also change this on the server
|
||||
*/
|
||||
watch(allowUserMounting, () => {
|
||||
const backupValue = !allowUserMounting.value
|
||||
window.OCP.AppConfig.setValue(
|
||||
'files_external',
|
||||
'allow_user_mounting',
|
||||
allowUserMounting.value ? 'yes' : 'no',
|
||||
{
|
||||
success: () => showSuccess(t('files_external', 'Saved')),
|
||||
error: () => {
|
||||
allowUserMounting.value = backupValue
|
||||
showError(t('files_external', 'Error while saving'))
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* Save list of allowed backends on the server
|
||||
*
|
||||
* @param newValue - The new changed value
|
||||
* @param oldValue - The old value for resetting on failure
|
||||
*/
|
||||
watch(allowedBackends, (newValue, oldValue) => {
|
||||
// save to server
|
||||
window.OCP.AppConfig.setValue(
|
||||
'files_external',
|
||||
'user_mounting_backends',
|
||||
newValue.join(','),
|
||||
{
|
||||
success: () => showSuccess(t('files_external', 'Saved allowed backends')),
|
||||
error: () => {
|
||||
showError(t('files_external', 'Failed to save allowed backends'))
|
||||
allowedBackends.value = oldValue
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form>
|
||||
<h3 :class="$style.userMountSettings__heading">
|
||||
{{ t('files_external', 'Advanced options for external storage mounts') }}
|
||||
</h3>
|
||||
|
||||
<NcCheckboxRadioSwitch v-model="allowUserMounting" type="switch">
|
||||
{{ t('files_external', 'Allow people to mount external storage') }}
|
||||
</NcCheckboxRadioSwitch>
|
||||
|
||||
<fieldset v-show="allowUserMounting" :class="$style.userMountSettings__backends">
|
||||
<legend>
|
||||
{{ t('files_external', 'External storage backends people are allowed to mount') }}
|
||||
</legend>
|
||||
<template v-for="backend of availableBackends">
|
||||
<NcCheckboxRadioSwitch
|
||||
v-if="!backend.deprecated"
|
||||
:key="backend.id"
|
||||
v-model="allowedBackends"
|
||||
:value="backend.id"
|
||||
name="allowUserMountingBackends[]">
|
||||
{{ backend.displayName }}
|
||||
</NcCheckboxRadioSwitch>
|
||||
<input
|
||||
v-else-if="backend.id in allowedBackends"
|
||||
:key="`${backend.id}-deprecated`"
|
||||
:data-deprecate-to="backend.deprecated"
|
||||
:value="backend.id"
|
||||
name="allowUserMountingBackends[]"
|
||||
type="hidden">
|
||||
</template>
|
||||
</fieldset>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<style module>
|
||||
.userMountSettings__heading {
|
||||
font-weight: bold;
|
||||
font-size: 1.2rem;
|
||||
|
||||
margin-block-start: var(--default-clickable-area);
|
||||
}
|
||||
|
||||
.userMountSettings__backends {
|
||||
--padding: calc((var(--default-clickable-area) - 20px) / 2 + var(--default-grid-baseline));
|
||||
margin-block-start: var(--padding);
|
||||
margin-inline-start: var(--padding);
|
||||
|
||||
legend {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
10
apps/files_external/src/settings-main.ts
Normal file
10
apps/files_external/src/settings-main.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import FilesExternalApp from './views/FilesExternalSettings.vue'
|
||||
|
||||
const app = createApp(FilesExternalApp)
|
||||
app.mount('#files-external')
|
||||
File diff suppressed because it is too large
Load diff
57
apps/files_external/src/store/storages.ts
Normal file
57
apps/files_external/src/store/storages.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { IStorage } from '../types.d.ts'
|
||||
|
||||
import axios from '@nextcloud/axios'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useStorages = defineStore('files_external--storages', {
|
||||
state() {
|
||||
return {
|
||||
globalStorages: [] as IStorage[],
|
||||
userStorages: [] as IStorage[],
|
||||
}
|
||||
},
|
||||
|
||||
getters: {
|
||||
allStorages(state) {
|
||||
return [...state.globalStorages, state.userStorages]
|
||||
},
|
||||
},
|
||||
|
||||
actions: {
|
||||
async loadGlobalStorages() {
|
||||
const url = 'apps/files_external/globalstorages'
|
||||
const { data } = await axios.get<IStorage[]>(generateUrl(url))
|
||||
|
||||
this.globalStorages = data
|
||||
},
|
||||
},
|
||||
/* result = Object.values(result);
|
||||
var onCompletion = jQuery.Deferred();
|
||||
var $rows = $();
|
||||
result.forEach(function(storageParams) {
|
||||
storageParams.mountPoint = (storageParams.mountPoint === '/')? '/' : storageParams.mountPoint.substr(1); // trim leading slash
|
||||
var storageConfig = new self._storageConfigClass();
|
||||
_.extend(storageConfig, storageParams);
|
||||
var $tr = self.newStorage(storageConfig, onCompletion, true);
|
||||
|
||||
// don't recheck config automatically when there are a large number of storages
|
||||
if (result.length < 20) {
|
||||
self.recheckStorageConfig($tr);
|
||||
} else {
|
||||
self.updateStatus($tr, StorageConfig.Status.INDETERMINATE, t('files_external', 'Automatic status checking is disabled due to the large number of configured storages, click to check status'));
|
||||
}
|
||||
$rows = $rows.add($tr);
|
||||
});
|
||||
initApplicableUsersMultiselect($rows.find('.applicableUsers'), this._userListLimit);
|
||||
self.$el.find('tr#addMountPoint').before($rows);
|
||||
onCompletion.resolve();
|
||||
onLoaded2.resolve();
|
||||
}
|
||||
}, */
|
||||
})
|
||||
21
apps/files_external/src/types.d.ts
vendored
Normal file
21
apps/files_external/src/types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export interface IStorage {
|
||||
id?: number
|
||||
|
||||
mountPoint: string
|
||||
backend: string
|
||||
authMechanism: string
|
||||
backendOptions: Record<string, unknown>
|
||||
priority?: number
|
||||
applicableUsers?: string[]
|
||||
applicableGroups?: string[]
|
||||
mountOptions?: Record<string, unknown>
|
||||
status?: number
|
||||
statusMessage?: string
|
||||
userProvided: bool
|
||||
type: 'personal' | 'system'
|
||||
}
|
||||
3
apps/files_external/src/utils/logger.ts
Normal file
3
apps/files_external/src/utils/logger.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { getLoggerBuilder } from '@nextcloud/logger'
|
||||
|
||||
export default getLoggerBuilder().setApp('files_external').build()
|
||||
88
apps/files_external/src/views/ExternalStoragesSection.vue
Normal file
88
apps/files_external/src/views/ExternalStoragesSection.vue
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
<script setup lang="ts">
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { n, t } from '@nextcloud/l10n'
|
||||
import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent'
|
||||
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
|
||||
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
|
||||
import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection'
|
||||
import UserMountSettings from '../components/UserMountSettings.vue'
|
||||
import filesExternalSvg from '../../img/app-dark.svg?raw'
|
||||
|
||||
const settings = loadState('files_external', 'settings', {
|
||||
docUrl: '',
|
||||
dependencyIssues: {
|
||||
messages: null as string[] | null,
|
||||
modules: null as Record<string, string[]> | null,
|
||||
},
|
||||
isAdmin: false,
|
||||
})
|
||||
|
||||
/** List of dependency issue messages */
|
||||
const dependencyIssues = settings.dependencyIssues?.messages ?? []
|
||||
/** Map of missing modules -> list of dependant backends */
|
||||
const missingModules = settings.dependencyIssues?.modules ?? {}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NcSettingsSection
|
||||
:doc-url="settings.docUrl"
|
||||
:name="t('files_external', 'External storage')"
|
||||
:description="t('files_external', 'External storage enables you to mount external storage services and devices as secondary Nextcloud storage devices. You may also allow people to mount their own external storage services.')">
|
||||
<!-- Dependency error messages -->
|
||||
<NcNoteCard
|
||||
v-for="message, index of dependencyIssues"
|
||||
:key="index"
|
||||
type="error">
|
||||
{{ message }}
|
||||
</NcNoteCard>
|
||||
|
||||
<!-- Missing modules for backends -->
|
||||
<NcNoteCard
|
||||
v-for="(dependants, module) in missingModules"
|
||||
:key="module"
|
||||
type="warning">
|
||||
<p>
|
||||
<template v-if="module === 'curl'">
|
||||
{{ t('files_external', 'The cURL support in PHP is not enabled or installed.') }}
|
||||
</template>
|
||||
<template v-else-if="module === 'ftp'">
|
||||
{{ t('files_external', 'The FTP support in PHP is not enabled or installed.') }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ t('files_external', '{module} is not installed.', { module }) }}
|
||||
</template>
|
||||
{{ n(
|
||||
'files_external',
|
||||
'Please ask your system administrator to install it as otherwise mounting the following backend is not possible:',
|
||||
'Please ask your system administrator to install it as otherwise mounting the following backends is not possible:',
|
||||
dependants.length,
|
||||
) }}
|
||||
</p>
|
||||
<ul class="files-external__dependant-list" :aria-label="t('files_external', 'Dependant backends')">
|
||||
<li v-for="backend of dependants" :key="backend">
|
||||
{{ backend }}
|
||||
</li>
|
||||
</ul>
|
||||
</NcNoteCard>
|
||||
|
||||
<!-- For user settings if the user has no permission or for user and admin settings if no storage was configured -->
|
||||
<NcEmptyContent :description="t('files_external', 'No external storage configured or you do not have the permission to configure them')">
|
||||
<template #icon>
|
||||
<NcIconSvgWrapper :svg="filesExternalSvg" :size="64" />
|
||||
</template>
|
||||
</NcEmptyContent>
|
||||
|
||||
<UserMountSettings v-if="settings.isAdmin" />
|
||||
</NcSettingsSection>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.files-external__dependant-list {
|
||||
list-style: disc;
|
||||
margin-inline-start: 22px;
|
||||
}
|
||||
</style>
|
||||
13
apps/files_external/src/views/FilesExternalSettings.vue
Normal file
13
apps/files_external/src/views/FilesExternalSettings.vue
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
<script setup lang="ts">
|
||||
import ExternalStoragesSection from './ExternalStoragesSection.vue'
|
||||
import GlobalCredentialsSection from './GlobalCredentialsSection.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ExternalStoragesSection />
|
||||
<GlobalCredentialsSection />
|
||||
</template>
|
||||
103
apps/files_external/src/views/GlobalCredentialsSection.vue
Normal file
103
apps/files_external/src/views/GlobalCredentialsSection.vue
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<script setup lang="ts">
|
||||
import axios from '@nextcloud/axios'
|
||||
import { showError, showSuccess } from '@nextcloud/dialogs'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { addPasswordConfirmationInterceptors, PwdConfirmationMode } from '@nextcloud/password-confirmation'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { ref } from 'vue'
|
||||
import NcButton from '@nextcloud/vue/components/NcButton'
|
||||
import NcPasswordField from '@nextcloud/vue/components/NcPasswordField'
|
||||
import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection'
|
||||
import NcTextField from '@nextcloud/vue/components/NcTextField'
|
||||
import logger from '../utils/logger.ts'
|
||||
|
||||
const globalCredentials = loadState<{
|
||||
uid: string
|
||||
user: string
|
||||
password: string
|
||||
}>('files_external', 'global-credentials')
|
||||
|
||||
const loading = ref(false)
|
||||
const username = ref(globalCredentials.user)
|
||||
const password = ref(globalCredentials.password)
|
||||
|
||||
addPasswordConfirmationInterceptors(axios)
|
||||
|
||||
/**
|
||||
* Submit the global credentials form
|
||||
*/
|
||||
async function onSubmit() {
|
||||
try {
|
||||
loading.value = true
|
||||
const { data } = await axios.post<boolean>(generateUrl('apps/files_external/globalcredentials'), {
|
||||
// This is the UID of the user to save the credentials (admins can set that also for other users)
|
||||
uid: globalCredentials.uid,
|
||||
user: username.value,
|
||||
password: password.value,
|
||||
}, { confirmPassword: PwdConfirmationMode.Strict })
|
||||
if (data) {
|
||||
showSuccess(t('files_external', 'Global credentials saved'))
|
||||
return
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error(e as Error)
|
||||
// Error is handled below
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
// result was false so show an error
|
||||
showError(t('files_external', 'Could not save global credentials'))
|
||||
username.value = globalCredentials.user
|
||||
password.value = globalCredentials.password
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NcSettingsSection
|
||||
:name="t('files_external', 'Global credentials')"
|
||||
:description="t('files_external', 'Global credentials can be used to authenticate with multiple external storages that have the same credentials.')">
|
||||
<form
|
||||
id="global_credentials"
|
||||
:class="$style.globalCredentialsSectionForm"
|
||||
autocomplete="false"
|
||||
@submit.prevent="onSubmit">
|
||||
<NcTextField
|
||||
v-model="username"
|
||||
name="username"
|
||||
autocomplete="false"
|
||||
:label="t('files_external', 'Login')" />
|
||||
<NcPasswordField
|
||||
v-model="password"
|
||||
name="password"
|
||||
autocomplete="false"
|
||||
:label="t('files_external', 'Password')" />
|
||||
<NcButton
|
||||
:class="$style.globalCredentialsSectionForm__submit"
|
||||
:disabled="loading"
|
||||
variant="primary"
|
||||
type="submit">
|
||||
{{ loading ? t('files_external', 'Saving …') : t('files_external', 'Save') }}
|
||||
</NcButton>
|
||||
</form>
|
||||
</NcSettingsSection>
|
||||
</template>
|
||||
|
||||
<style module>
|
||||
.globalCredentialsSectionForm {
|
||||
max-width: 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: end;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.globalCredentialsSectionForm__submit {
|
||||
min-width: max(40%, 44px);
|
||||
}
|
||||
</style>
|
||||
240
apps/files_external/templates/legacy-settings.php
Normal file
240
apps/files_external/templates/legacy-settings.php
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
<?php
|
||||
use OCA\Files_External\Lib\Auth\AuthMechanism;
|
||||
use OCA\Files_External\Lib\Backend\Backend;
|
||||
use OCA\Files_External\Lib\DefinitionParameter;
|
||||
use OCA\Files_External\Service\BackendService;
|
||||
|
||||
/** @var array $_ */
|
||||
|
||||
$canCreateMounts = $_['visibilityType'] === BackendService::VISIBILITY_ADMIN || $_['allowUserMounting'];
|
||||
|
||||
$l->t('Enable encryption');
|
||||
$l->t('Enable previews');
|
||||
$l->t('Enable sharing');
|
||||
$l->t('Check for changes');
|
||||
$l->t('Never');
|
||||
$l->t('Once every direct access');
|
||||
$l->t('Read only');
|
||||
|
||||
script('files_external', [
|
||||
'settings',
|
||||
'templates'
|
||||
]);
|
||||
style('files_external', 'settings');
|
||||
|
||||
// load custom JS
|
||||
foreach ($_['backends'] as $backend) {
|
||||
/** @var Backend $backend */
|
||||
$scripts = $backend->getCustomJs();
|
||||
foreach ($scripts as $script) {
|
||||
script('files_external', $script);
|
||||
}
|
||||
}
|
||||
foreach ($_['authMechanisms'] as $authMechanism) {
|
||||
/** @var AuthMechanism $authMechanism */
|
||||
$scripts = $authMechanism->getCustomJs();
|
||||
foreach ($scripts as $script) {
|
||||
script('files_external', $script);
|
||||
}
|
||||
}
|
||||
|
||||
function writeParameterInput($parameter, $options, $classes = []) {
|
||||
$value = '';
|
||||
if (isset($options[$parameter->getName()])) {
|
||||
$value = $options[$parameter->getName()];
|
||||
}
|
||||
$placeholder = $parameter->getText();
|
||||
$is_optional = $parameter->isFlagSet(DefinitionParameter::FLAG_OPTIONAL);
|
||||
|
||||
switch ($parameter->getType()) {
|
||||
case DefinitionParameter::VALUE_PASSWORD: ?>
|
||||
<?php if ($is_optional) {
|
||||
$classes[] = 'optional';
|
||||
} ?>
|
||||
<input type="password"
|
||||
<?php if (!empty($classes)): ?> class="<?php p(implode(' ', $classes)); ?>"<?php endif; ?>
|
||||
data-parameter="<?php p($parameter->getName()); ?>"
|
||||
value="<?php p($value); ?>"
|
||||
placeholder="<?php p($placeholder); ?>"
|
||||
/>
|
||||
<?php
|
||||
break;
|
||||
case DefinitionParameter::VALUE_BOOLEAN: ?>
|
||||
<?php $checkboxId = uniqid('checkbox_'); ?>
|
||||
<div>
|
||||
<label>
|
||||
<input type="checkbox"
|
||||
id="<?php p($checkboxId); ?>"
|
||||
<?php if (!empty($classes)): ?> class="checkbox <?php p(implode(' ', $classes)); ?>"<?php endif; ?>
|
||||
data-parameter="<?php p($parameter->getName()); ?>"
|
||||
<?php if ($value === true): ?> checked="checked"<?php endif; ?>
|
||||
/>
|
||||
<?php p($placeholder); ?>
|
||||
</label>
|
||||
</div>
|
||||
<?php
|
||||
break;
|
||||
case DefinitionParameter::VALUE_HIDDEN: ?>
|
||||
<input type="hidden"
|
||||
<?php if (!empty($classes)): ?> class="<?php p(implode(' ', $classes)); ?>"<?php endif; ?>
|
||||
data-parameter="<?php p($parameter->getName()); ?>"
|
||||
value="<?php p($value); ?>"
|
||||
/>
|
||||
<?php
|
||||
break;
|
||||
default: ?>
|
||||
<?php if ($is_optional) {
|
||||
$classes[] = 'optional';
|
||||
} ?>
|
||||
<input type="text"
|
||||
<?php if (!empty($classes)): ?> class="<?php p(implode(' ', $classes)); ?>"<?php endif; ?>
|
||||
data-parameter="<?php p($parameter->getName()); ?>"
|
||||
value="<?php p($value); ?>"
|
||||
placeholder="<?php p($placeholder); ?>"
|
||||
/>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="emptyfilelist emptycontent hidden">
|
||||
<div class="icon-external"></div>
|
||||
<h2><?php p($l->t('No external storage configured or you don\'t have the permission to configure them')); ?></h2>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$canCreateNewLocalStorage = \OC::$server->getConfig()->getSystemValue('files_external_allow_create_new_local', true);
|
||||
?>
|
||||
<form data-can-create="<?php echo $canCreateMounts?'true':'false' ?>" data-can-create-local="<?php echo $canCreateNewLocalStorage?'true':'false' ?>" id="files_external" class="section" data-encryption-enabled="<?php echo $_['encryptionEnabled']?'true': 'false'; ?>">
|
||||
<h2 class="inlineblock" data-anchor-name="external-storage"><?php p($l->t('External storage')); ?></h2>
|
||||
<a target="_blank" rel="noreferrer" class="icon-info" title="<?php p($l->t('Open documentation'));?>" href="<?php p(link_to_docs('admin-external-storage')); ?>"></a>
|
||||
<p class="settings-hint"><?php p($l->t('External storage enables you to mount external storage services and devices as secondary Nextcloud storage devices. You may also allow people to mount their own external storage services.')); ?></p>
|
||||
<?php if (isset($_['dependencies']) and ($_['dependencies'] !== '') and $canCreateMounts) {
|
||||
print_unescaped('' . $_['dependencies'] . '');
|
||||
} ?>
|
||||
<table id="externalStorage" class="grid" data-admin='<?php print_unescaped(json_encode($_['visibilityType'] === BackendService::VISIBILITY_ADMIN)); ?>'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th><?php p($l->t('Folder name')); ?></th>
|
||||
<th><?php p($l->t('External storage')); ?></th>
|
||||
<th><?php p($l->t('Authentication')); ?></th>
|
||||
<th><?php p($l->t('Configuration')); ?></th>
|
||||
<?php if ($_['visibilityType'] === BackendService::VISIBILITY_ADMIN) {
|
||||
print_unescaped('<th>' . $l->t('Available for') . '</th>');
|
||||
} ?>
|
||||
<th> </th>
|
||||
<th> </th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="externalStorageLoading">
|
||||
<td colspan="8">
|
||||
<span id="externalStorageLoading" class="icon icon-loading"></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="addMountPoint"
|
||||
<?php if (!$canCreateMounts): ?>
|
||||
style="display: none;"
|
||||
<?php endif; ?>
|
||||
>
|
||||
<td class="status">
|
||||
<span data-placement="right" title="<?php p($l->t('Click to recheck the configuration')); ?>"></span>
|
||||
</td>
|
||||
<td class="mountPoint"><input type="text" name="mountPoint" value=""
|
||||
placeholder="<?php p($l->t('Folder name')); ?>">
|
||||
</td>
|
||||
<td class="backend">
|
||||
<select id="selectBackend" class="selectBackend" data-configurations='<?php p(json_encode($_['backends'])); ?>'>
|
||||
<option value="" disabled selected
|
||||
style="display:none;">
|
||||
<?php p($l->t('Add storage')); ?>
|
||||
</option>
|
||||
<?php
|
||||
$sortedBackends = array_filter($_['backends'], function ($backend) use ($_) {
|
||||
return $backend->isVisibleFor($_['visibilityType']);
|
||||
});
|
||||
uasort($sortedBackends, function ($a, $b) {
|
||||
return strcasecmp($a->getText(), $b->getText());
|
||||
});
|
||||
?>
|
||||
<?php foreach ($sortedBackends as $backend): ?>
|
||||
<?php if ($backend->getDeprecateTo() || (!$canCreateNewLocalStorage && $backend->getIdentifier() == 'local')) {
|
||||
continue;
|
||||
} // ignore deprecated backends?>
|
||||
<option value="<?php p($backend->getIdentifier()); ?>"><?php p($backend->getText()); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</td>
|
||||
<td class="authentication" data-mechanisms='<?php p(json_encode($_['authMechanisms'])); ?>'></td>
|
||||
<td class="configuration"></td>
|
||||
<?php if ($_['visibilityType'] === BackendService::VISIBILITY_ADMIN): ?>
|
||||
<td class="applicable" align="right">
|
||||
<label><input type="checkbox" class="applicableToAllUsers" checked="" /><?php p($l->t('All people')); ?></label>
|
||||
<div class="applicableUsersContainer">
|
||||
<input type="hidden" class="applicableUsers" style="width:20em;" value="" />
|
||||
</div>
|
||||
</td>
|
||||
<?php endif; ?>
|
||||
<td class="mountOptionsToggle hidden">
|
||||
<button type="button" class="icon-more" aria-expanded="false" title="<?php p($l->t('Advanced settings')); ?>"></button>
|
||||
<input type="hidden" class="mountOptions" value="" />
|
||||
</td>
|
||||
<td class="save hidden">
|
||||
<button type="button" class="icon-checkmark" title="<?php p($l->t('Save')); ?>"></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php if ($_['visibilityType'] === BackendService::VISIBILITY_ADMIN): ?>
|
||||
<input type="checkbox" name="allowUserMounting" id="allowUserMounting" class="checkbox"
|
||||
value="1" <?php if ($_['allowUserMounting']) {
|
||||
print_unescaped(' checked="checked"');
|
||||
} ?> />
|
||||
<label for="allowUserMounting"><?php p($l->t('Allow people to mount external storage')); ?></label> <span id="userMountingMsg" class="msg"></span>
|
||||
|
||||
<p id="userMountingBackends"<?php if (!$_['allowUserMounting']): ?> class="hidden"<?php endif; ?>>
|
||||
<?php
|
||||
$userBackends = array_filter($_['backends'], function ($backend) {
|
||||
return $backend->isAllowedVisibleFor(BackendService::VISIBILITY_PERSONAL);
|
||||
});
|
||||
?>
|
||||
<?php $i = 0;
|
||||
foreach ($userBackends as $backend): ?>
|
||||
<?php if ($deprecateTo = $backend->getDeprecateTo()): ?>
|
||||
<input type="hidden" id="allowUserMountingBackends<?php p($i); ?>" name="allowUserMountingBackends[]" value="<?php p($backend->getIdentifier()); ?>" data-deprecate-to="<?php p($deprecateTo->getIdentifier()); ?>" />
|
||||
<?php else: ?>
|
||||
<input type="checkbox" id="allowUserMountingBackends<?php p($i); ?>" class="checkbox" name="allowUserMountingBackends[]" value="<?php p($backend->getIdentifier()); ?>" <?php if ($backend->isVisibleFor(BackendService::VISIBILITY_PERSONAL)) {
|
||||
print_unescaped(' checked="checked"');
|
||||
} ?> />
|
||||
<label for="allowUserMountingBackends<?php p($i); ?>"><?php p($backend->getText()); ?></label> <br />
|
||||
<?php endif; ?>
|
||||
<?php $i++; ?>
|
||||
<?php endforeach; ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
|
||||
<div class="followupsection">
|
||||
<form autocomplete="false" action="#"
|
||||
id="global_credentials" method="post"
|
||||
class="<?php if (isset($_['visibilityType']) && $_['visibilityType'] === BackendService::VISIBILITY_PERSONAL) {
|
||||
print_unescaped('global_credentials__personal');
|
||||
} ?>">
|
||||
<h2><?php p($l->t('Global credentials')); ?></h2>
|
||||
<p class="settings-hint"><?php p($l->t('Global credentials can be used to authenticate with multiple external storages that have the same credentials.')); ?></p>
|
||||
<input type="text" name="username"
|
||||
autocomplete="false"
|
||||
value="<?php p($_['globalCredentials']['user']); ?>"
|
||||
placeholder="<?php p($l->t('Login')) ?>"/>
|
||||
<input type="password" name="password"
|
||||
autocomplete="false"
|
||||
value="<?php p($_['globalCredentials']['password']); ?>"
|
||||
placeholder="<?php p($l->t('Password')) ?>"/>
|
||||
<input type="hidden" name="uid"
|
||||
value="<?php p($_['globalCredentialsUid']); ?>"/>
|
||||
<input type="submit" value="<?php p($l->t('Save')) ?>"/>
|
||||
</form>
|
||||
</div>
|
||||
|
|
@ -1,186 +1,7 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2012-2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
use OCA\Files_External\Lib\Auth\AuthMechanism;
|
||||
use OCA\Files_External\Lib\Backend\Backend;
|
||||
use OCA\Files_External\Service\BackendService;
|
||||
|
||||
/** @var array $_ */
|
||||
|
||||
$canCreateMounts = $_['visibilityType'] === BackendService::VISIBILITY_ADMIN || $_['allowUserMounting'];
|
||||
|
||||
$l->t('Enable encryption');
|
||||
$l->t('Enable previews');
|
||||
$l->t('Enable sharing');
|
||||
$l->t('Check for changes');
|
||||
$l->t('Never');
|
||||
$l->t('Once every direct access');
|
||||
$l->t('Read only');
|
||||
|
||||
\OCP\Util::addScript('files_external', 'settings');
|
||||
\OCP\Util::addScript('files_external', 'templates');
|
||||
style('files_external', 'settings');
|
||||
|
||||
// load custom JS
|
||||
foreach ($_['backends'] as $backend) {
|
||||
/** @var Backend $backend */
|
||||
$scripts = $backend->getCustomJs();
|
||||
foreach ($scripts as $script) {
|
||||
script('files_external', $script);
|
||||
}
|
||||
}
|
||||
foreach ($_['authMechanisms'] as $authMechanism) {
|
||||
/** @var AuthMechanism $authMechanism */
|
||||
$scripts = $authMechanism->getCustomJs();
|
||||
foreach ($scripts as $script) {
|
||||
script('files_external', $script);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<div class="emptyfilelist emptycontent hidden">
|
||||
<div class="icon-external"></div>
|
||||
<h2><?php p($l->t('No external storage configured or you don\'t have the permission to configure them')); ?></h2>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$canCreateNewLocalStorage = \OCP\Server::get(\OCP\IConfig::class)->getSystemValue('files_external_allow_create_new_local', true);
|
||||
?>
|
||||
<form data-can-create="<?php echo $canCreateMounts?'true':'false' ?>" data-can-create-local="<?php echo $canCreateNewLocalStorage?'true':'false' ?>" id="files_external" class="section" data-encryption-enabled="<?php echo $_['encryptionEnabled']?'true': 'false'; ?>">
|
||||
<h2 class="inlineblock" data-anchor-name="external-storage"><?php p($l->t('External storage')); ?></h2>
|
||||
<a target="_blank" rel="noreferrer" class="icon-info" title="<?php p($l->t('Open documentation'));?>" href="<?php p(link_to_docs('admin-external-storage')); ?>"></a>
|
||||
<p class="settings-hint"><?php p($l->t('External storage enables you to mount external storage services and devices as secondary Nextcloud storage devices. You may also allow people to mount their own external storage services.')); ?></p>
|
||||
<?php if (isset($_['dependencies']) && ($_['dependencies'] !== '') && $canCreateMounts) {
|
||||
print_unescaped('' . $_['dependencies'] . '');
|
||||
} ?>
|
||||
<table id="externalStorage" class="grid" data-admin='<?php print_unescaped(json_encode($_['visibilityType'] === BackendService::VISIBILITY_ADMIN)); ?>'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th><?php p($l->t('Folder name')); ?></th>
|
||||
<th><?php p($l->t('External storage')); ?></th>
|
||||
<th><?php p($l->t('Authentication')); ?></th>
|
||||
<th><?php p($l->t('Configuration')); ?></th>
|
||||
<?php if ($_['visibilityType'] === BackendService::VISIBILITY_ADMIN) {
|
||||
print_unescaped('<th>' . $l->t('Available for') . '</th>');
|
||||
} ?>
|
||||
<th> </th>
|
||||
<th> </th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="externalStorageLoading">
|
||||
<td colspan="8">
|
||||
<span id="externalStorageLoading" class="icon icon-loading"></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="addMountPoint"
|
||||
<?php if (!$canCreateMounts): ?>
|
||||
style="display: none;"
|
||||
<?php endif; ?>
|
||||
>
|
||||
<td class="status">
|
||||
<span data-placement="right" title="<?php p($l->t('Click to recheck the configuration')); ?>" style="display: none;"></span>
|
||||
</td>
|
||||
<td class="mountPoint"><input type="text" name="mountPoint" value=""
|
||||
placeholder="<?php p($l->t('Folder name')); ?>">
|
||||
</td>
|
||||
<td class="backend">
|
||||
<select id="selectBackend" class="selectBackend" data-configurations='<?php p(json_encode($_['backends'])); ?>'>
|
||||
<option value="" disabled selected
|
||||
style="display:none;">
|
||||
<?php p($l->t('Add storage')); ?>
|
||||
</option>
|
||||
<?php
|
||||
$sortedBackends = array_filter($_['backends'], function ($backend) use ($_) {
|
||||
return $backend->isVisibleFor($_['visibilityType']);
|
||||
});
|
||||
uasort($sortedBackends, function ($a, $b) {
|
||||
return strcasecmp($a->getText(), $b->getText());
|
||||
});
|
||||
?>
|
||||
<?php foreach ($sortedBackends as $backend): ?>
|
||||
<?php if ($backend->getDeprecateTo() || (!$canCreateNewLocalStorage && $backend->getIdentifier() == 'local')) {
|
||||
continue;
|
||||
} // ignore deprecated backends?>
|
||||
<option value="<?php p($backend->getIdentifier()); ?>"><?php p($backend->getText()); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</td>
|
||||
<td class="authentication" data-mechanisms='<?php p(json_encode($_['authMechanisms'])); ?>'></td>
|
||||
<td class="configuration"></td>
|
||||
<?php if ($_['visibilityType'] === BackendService::VISIBILITY_ADMIN): ?>
|
||||
<td class="applicable" align="right">
|
||||
<label><input type="checkbox" class="applicableToAllUsers" checked="" /><?php p($l->t('All people')); ?></label>
|
||||
<div class="applicableUsersContainer">
|
||||
<input type="hidden" class="applicableUsers" style="width:20em;" value="" />
|
||||
</div>
|
||||
</td>
|
||||
<?php endif; ?>
|
||||
<td class="mountOptionsToggle hidden">
|
||||
<button type="button" class="icon-more" aria-expanded="false" title="<?php p($l->t('Advanced settings')); ?>"></button>
|
||||
<input type="hidden" class="mountOptions" value="" />
|
||||
</td>
|
||||
<td class="save hidden">
|
||||
<button type="button" class="icon-checkmark" title="<?php p($l->t('Save')); ?>"></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php if ($_['visibilityType'] === BackendService::VISIBILITY_ADMIN): ?>
|
||||
<input type="checkbox" name="allowUserMounting" id="allowUserMounting" class="checkbox"
|
||||
value="1" <?php if ($_['allowUserMounting']) {
|
||||
print_unescaped(' checked="checked"');
|
||||
} ?> />
|
||||
<label for="allowUserMounting"><?php p($l->t('Allow people to mount external storage')); ?></label> <span id="userMountingMsg" class="msg"></span>
|
||||
|
||||
<p id="userMountingBackends"<?php if (!$_['allowUserMounting']): ?> class="hidden"<?php endif; ?>>
|
||||
<?php
|
||||
$userBackends = array_filter($_['backends'], function ($backend) {
|
||||
return $backend->isAllowedVisibleFor(BackendService::VISIBILITY_PERSONAL);
|
||||
});
|
||||
?>
|
||||
<?php $i = 0;
|
||||
foreach ($userBackends as $backend): ?>
|
||||
<?php if ($deprecateTo = $backend->getDeprecateTo()): ?>
|
||||
<input type="hidden" id="allowUserMountingBackends<?php p($i); ?>" name="allowUserMountingBackends[]" value="<?php p($backend->getIdentifier()); ?>" data-deprecate-to="<?php p($deprecateTo->getIdentifier()); ?>" />
|
||||
<?php else: ?>
|
||||
<input type="checkbox" id="allowUserMountingBackends<?php p($i); ?>" class="checkbox" name="allowUserMountingBackends[]" value="<?php p($backend->getIdentifier()); ?>" <?php if ($backend->isVisibleFor(BackendService::VISIBILITY_PERSONAL)) {
|
||||
print_unescaped(' checked="checked"');
|
||||
} ?> />
|
||||
<label for="allowUserMountingBackends<?php p($i); ?>"><?php p($backend->getText()); ?></label> <br />
|
||||
<?php endif; ?>
|
||||
<?php $i++; ?>
|
||||
<?php endforeach; ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
|
||||
<div class="followupsection">
|
||||
<form autocomplete="false" action="#"
|
||||
id="global_credentials" method="post"
|
||||
class="<?php if (isset($_['visibilityType']) && $_['visibilityType'] === BackendService::VISIBILITY_PERSONAL) {
|
||||
print_unescaped('global_credentials__personal');
|
||||
} ?>">
|
||||
<h2><?php p($l->t('Global credentials')); ?></h2>
|
||||
<p class="settings-hint"><?php p($l->t('Global credentials can be used to authenticate with multiple external storages that have the same credentials.')); ?></p>
|
||||
<input type="text" name="username"
|
||||
autocomplete="false"
|
||||
value="<?php p($_['globalCredentials']['user']); ?>"
|
||||
placeholder="<?php p($l->t('Login')) ?>"/>
|
||||
<input type="password" name="password"
|
||||
autocomplete="false"
|
||||
value="<?php p($_['globalCredentials']['password']); ?>"
|
||||
placeholder="<?php p($l->t('Password')) ?>"/>
|
||||
<input type="hidden" name="uid"
|
||||
value="<?php p($_['globalCredentialsUid']); ?>"/>
|
||||
<input type="submit" value="<?php p($l->t('Save')) ?>"/>
|
||||
</form>
|
||||
</div>
|
||||
<div id="files-external"></div>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ const modules = {
|
|||
},
|
||||
files_external: {
|
||||
init: resolve(import.meta.dirname, 'apps/files_external/src', 'init.ts'),
|
||||
settings: resolve(import.meta.dirname, 'apps/files_external/src', 'settings.js'),
|
||||
settings: resolve(import.meta.dirname, 'apps/files_external/src', 'settings-main.ts'),
|
||||
},
|
||||
files_reminders: {
|
||||
init: resolve(import.meta.dirname, 'apps/files_reminders/src', 'files-init.ts'),
|
||||
|
|
|
|||
Loading…
Reference in a new issue