@brftech/filex-core 0.42.1 → 0.43.0
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/README.md +14 -2
- package/dist/ArchiveViewer-BqU761Wk.js +149 -0
- package/dist/ArchiveViewer-BqU761Wk.js.map +1 -0
- package/dist/CsvViewer-ByHVK5ga.js +172 -0
- package/dist/CsvViewer-ByHVK5ga.js.map +1 -0
- package/dist/{DrawioViewer-BuhZOAx6.js → DrawioViewer-Bgrj8-zG.js} +45 -44
- package/dist/DrawioViewer-Bgrj8-zG.js.map +1 -0
- package/dist/{EpubViewer-DS3mb3k8.js → EpubViewer-DJwUKlHW.js} +50 -50
- package/dist/EpubViewer-DJwUKlHW.js.map +1 -0
- package/dist/IpynbViewer-Bz0eJ4NS.js +184 -0
- package/dist/IpynbViewer-Bz0eJ4NS.js.map +1 -0
- package/dist/MermaidViewer-CEDTD_pp.js +136 -0
- package/dist/MermaidViewer-CEDTD_pp.js.map +1 -0
- package/dist/PsdViewer-CZcxUpZO.js +121 -0
- package/dist/PsdViewer-CZcxUpZO.js.map +1 -0
- package/dist/TiffViewer-BwI62CbQ.js +141 -0
- package/dist/TiffViewer-BwI62CbQ.js.map +1 -0
- package/dist/{Viewer3D-CIrOn63I.js → Viewer3D-DoeZcuMm.js} +16 -16
- package/dist/Viewer3D-DoeZcuMm.js.map +1 -0
- package/dist/filex-core.js +475 -219
- package/dist/filex-core.umd.cjs +165 -108
- package/dist/filex-core.umd.cjs.map +1 -1
- package/dist/fonts/caveat-latin-ext.woff2 +0 -0
- package/dist/fonts/caveat-latin.woff2 +0 -0
- package/dist/fonts/dancing-script-latin-ext.woff2 +0 -0
- package/dist/fonts/dancing-script-latin.woff2 +0 -0
- package/dist/fonts/homemade-apple-latin.woff2 +0 -0
- package/dist/fonts/source-serif-4-latin-ext.woff2 +0 -0
- package/dist/fonts/source-serif-4-latin.woff2 +0 -0
- package/dist/index-DuWnGuv5.js +35901 -0
- package/dist/index-DuWnGuv5.js.map +1 -0
- package/dist/index.d.ts +5241 -553
- package/dist/pdf-CaPYsOUo.js +21302 -0
- package/dist/pdf-CaPYsOUo.js.map +1 -0
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/FileExplorer.vue +1207 -343
- package/src/assets/fonts/LICENSE-Apache-2.0.txt +202 -0
- package/src/assets/fonts/LICENSE-OFL-1.1.txt +93 -0
- package/src/assets/fonts/README.md +58 -0
- package/src/assets/fonts/caveat-latin-ext.woff2 +0 -0
- package/src/assets/fonts/caveat-latin.woff2 +0 -0
- package/src/assets/fonts/dancing-script-latin-ext.woff2 +0 -0
- package/src/assets/fonts/dancing-script-latin.woff2 +0 -0
- package/src/assets/fonts/homemade-apple-latin.woff2 +0 -0
- package/src/assets/fonts/source-serif-4-latin-ext.woff2 +0 -0
- package/src/assets/fonts/source-serif-4-latin.woff2 +0 -0
- package/src/components/AdvancedSearch.vue +49 -0
- package/src/components/Breadcrumb.vue +5 -5
- package/src/components/ChoiceButtons.vue +152 -0
- package/src/components/CommandPalette.vue +5 -5
- package/src/components/ConnectionGuideView.vue +1 -1
- package/src/components/ConnectionNotice.vue +59 -0
- package/src/components/ConnectionsPanel.vue +30 -568
- package/src/components/ContextMenu.vue +31 -5
- package/src/components/DataTable.vue +1331 -0
- package/src/components/FilePane.vue +3 -0
- package/src/components/FilterBar.vue +12 -5
- package/src/components/GalleryView.vue +50 -2
- package/src/components/GridView.vue +51 -3
- package/src/components/HomeView.vue +18 -6
- package/src/components/InspectorPanel.vue +134 -17
- package/src/components/ListView.vue +366 -900
- package/src/components/LogoMark.vue +57 -0
- package/src/components/NFSExportsPanel.vue +76 -28
- package/src/components/OnboardingTour.vue +19 -7
- package/src/components/OperationsCenter.vue +77 -1
- package/src/components/PendingOpsTray.vue +59 -5
- package/src/components/PresenceBar.vue +7 -7
- package/src/components/ProductVersion.vue +31 -0
- package/src/components/QuickLook.vue +10 -14
- package/src/components/RecentlyOpened.vue +47 -17
- package/src/components/RecoveryKeyModal.vue +1 -1
- package/src/components/RowActions.vue +134 -0
- package/src/components/S3KeysPanel.vue +98 -56
- package/src/components/SSHKeysPanel.vue +79 -26
- package/src/components/SideNav.vue +260 -96
- package/src/components/StarButton.vue +1 -0
- package/src/components/StorageFields.vue +74 -6
- package/src/components/StorageTags.vue +62 -0
- package/src/components/TabBar.vue +1 -1
- package/src/components/TagKindIcon.vue +56 -0
- package/src/components/TagPicker.vue +226 -40
- package/src/components/ThemePalette.vue +4 -4
- package/src/components/TimeZonePicker.vue +29 -10
- package/src/components/TokensPanel.vue +82 -27
- package/src/components/Toolbar.vue +28 -6
- package/src/components/plugin/PluginConfirmModal.vue +54 -0
- package/src/components/plugin/PluginInspectorSection.vue +149 -0
- package/src/components/plugin/PluginPageView.vue +339 -0
- package/src/components/plugin/PluginViewModal.vue +159 -0
- package/src/components/plugin/SurfaceConversation.vue +76 -0
- package/src/components/plugin/SurfaceFooterButtons.vue +73 -0
- package/src/components/plugin/SurfaceRenderer.vue +299 -0
- package/src/components/plugin/SurfaceSections.vue +91 -0
- package/src/components/plugin/nodes/PdfFieldEditor.vue +509 -0
- package/src/components/plugin/nodes/SurfaceFileChooser.vue +94 -0
- package/src/components/plugin/nodes/SurfaceForm.vue +82 -0
- package/src/components/plugin/nodes/SurfaceList.vue +127 -0
- package/src/components/plugin/nodes/SurfacePdfFields.vue +1285 -0
- package/src/components/plugin/nodes/SurfacePeoplePicker.vue +213 -0
- package/src/components/plugin/nodes/SurfacePinInput.vue +57 -0
- package/src/components/plugin/nodes/SurfacePreview.vue +119 -0
- package/src/components/plugin/nodes/SurfaceProgress.vue +48 -0
- package/src/components/plugin/nodes/SurfaceSignaturePad.vue +472 -0
- package/src/components/plugin/nodes/SurfaceSteps.vue +53 -0
- package/src/components/public/PublicLanguagePicker.vue +58 -0
- package/src/components/public/PublicLinkPage.vue +286 -0
- package/src/components/public/PublicLinkPreview.vue +85 -0
- package/src/components/public/PublicPinGate.vue +108 -0
- package/src/components/public/PublicRequestBody.vue +204 -0
- package/src/components/public/PublicShareBody.vue +188 -0
- package/src/components/public/PublicShell.vue +332 -0
- package/src/composables/useConnections.ts +8 -2
- package/src/composables/useFileApi.ts +183 -44
- package/src/composables/useLocale.ts +277 -41
- package/src/composables/useNFSExports.ts +8 -10
- package/src/composables/useOperations.ts +44 -3
- package/src/composables/usePendingOps.ts +59 -8
- package/src/composables/usePluginActions.ts +121 -0
- package/src/composables/usePluginSurface.ts +223 -0
- package/src/composables/usePublicBranding.ts +172 -0
- package/src/composables/usePublicLink.ts +573 -0
- package/src/composables/usePublicPage.ts +61 -0
- package/src/composables/useS3Keys.ts +17 -12
- package/src/composables/useSSHKeys.ts +8 -10
- package/src/composables/useTokens.ts +8 -12
- package/src/composables/useUploadChunked.ts +18 -8
- package/src/composables/useViewerFetch.ts +6 -3
- package/src/index.ts +404 -8
- package/src/lib/actionIcons.ts +46 -1
- package/src/lib/advSearch.ts +14 -7
- package/src/lib/appLock.ts +130 -0
- package/src/lib/connection.ts +110 -0
- package/src/lib/destinationTree.ts +19 -3
- package/src/lib/direction.ts +353 -0
- package/src/lib/dragGhost.ts +6 -0
- package/src/lib/dropLimits.ts +69 -0
- package/src/lib/errorWords.ts +394 -0
- package/src/lib/externalReach.ts +5 -1
- package/src/lib/fileFilters.ts +7 -2
- package/src/lib/fileIcons.ts +4 -0
- package/src/lib/internalPaths.ts +77 -0
- package/src/lib/listInput.ts +64 -0
- package/src/lib/listing.ts +58 -23
- package/src/lib/modalStack.ts +26 -0
- package/src/lib/nodeRow.ts +121 -0
- package/src/lib/pdfFieldRules.ts +126 -0
- package/src/lib/pdfFields.ts +589 -0
- package/src/lib/pdfFieldsGeom.ts +203 -0
- package/src/lib/pdfjsLoader.ts +51 -0
- package/src/lib/personName.ts +47 -0
- package/src/lib/pluginApplies.ts +123 -0
- package/src/lib/pluginLabel.ts +72 -0
- package/src/lib/pluginMenu.ts +253 -0
- package/src/lib/pluginPage.ts +58 -0
- package/src/lib/plural.ts +181 -0
- package/src/lib/popupLayer.ts +103 -0
- package/src/lib/prefs.ts +563 -0
- package/src/lib/productVersion.ts +35 -0
- package/src/lib/publicLayout.ts +39 -0
- package/src/lib/serviceGate.ts +104 -0
- package/src/lib/shareTtl.ts +25 -11
- package/src/lib/signFonts.ts +85 -0
- package/src/lib/signaturePad.ts +111 -0
- package/src/lib/sortOrder.ts +100 -38
- package/src/lib/star.ts +7 -1
- package/src/lib/surfaceCell.ts +41 -0
- package/src/lib/surfaceConditions.ts +156 -0
- package/src/lib/surfaceOpen.ts +106 -0
- package/src/lib/surfaceSteps.ts +68 -0
- package/src/lib/surfaceValues.ts +191 -0
- package/src/lib/symlink.ts +150 -0
- package/src/lib/tableColumns.ts +493 -0
- package/src/lib/tableEnv.ts +34 -0
- package/src/lib/tablePrefs.ts +84 -0
- package/src/lib/tags.ts +121 -12
- package/src/lib/themes.ts +286 -18
- package/src/lib/tour.ts +152 -0
- package/src/lib/trashEmpty.ts +111 -0
- package/src/lib/trashTimeLeft.ts +23 -0
- package/src/lib/uiLocales.ts +460 -0
- package/src/lib/viewPrefs.ts +696 -660
- package/src/lib/viewPrefsHttp.ts +78 -0
- package/src/locales/en.ts +522 -119
- package/src/locales/resolve.ts +17 -8
- package/src/locales/tr.ts +539 -155
- package/src/modals/ConvertModal.vue +62 -11
- package/src/modals/DestinationPickerModal.vue +59 -16
- package/src/modals/Modal.vue +113 -23
- package/src/modals/NewDocumentModal.vue +16 -6
- package/src/modals/NewFolderModal.vue +8 -0
- package/src/modals/PermissionsModal.vue +272 -163
- package/src/modals/PreviewModal.vue +119 -20
- package/src/modals/RenameModal.vue +37 -1
- package/src/styles/base.css +2895 -241
- package/src/styles/sign-fonts.css +118 -0
- package/src/styles/variables.css +60 -0
- package/src/types/Connections.ts +17 -0
- package/src/types/ExplorerConfig.ts +117 -1
- package/src/types/FileNode.ts +58 -0
- package/src/types/Plugins.ts +494 -0
- package/src/types/Public.ts +212 -0
- package/src/viewers/ArchiveViewer.vue +93 -48
- package/src/viewers/CsvViewer.vue +99 -53
- package/src/viewers/DrawioViewer.vue +35 -9
- package/src/viewers/EpubViewer.vue +7 -8
- package/src/viewers/IpynbViewer.vue +3 -1
- package/src/viewers/MermaidViewer.vue +4 -5
- package/src/viewers/PdfViewer.vue +8 -17
- package/src/viewers/PsdViewer.vue +2 -4
- package/src/viewers/TiffViewer.vue +4 -5
- package/src/viewers/Viewer3D.vue +1 -1
- package/LICENSE +0 -21
- package/dist/ArchiveViewer-CLNevfPE.js +0 -124
- package/dist/ArchiveViewer-CLNevfPE.js.map +0 -1
- package/dist/CsvViewer-K8XA3z7N.js +0 -141
- package/dist/CsvViewer-K8XA3z7N.js.map +0 -1
- package/dist/DrawioViewer-BuhZOAx6.js.map +0 -1
- package/dist/EpubViewer-DS3mb3k8.js.map +0 -1
- package/dist/IpynbViewer-BTk7p4Dn.js +0 -184
- package/dist/IpynbViewer-BTk7p4Dn.js.map +0 -1
- package/dist/MermaidViewer-DqucH_io.js +0 -137
- package/dist/MermaidViewer-DqucH_io.js.map +0 -1
- package/dist/PsdViewer-CwiVENmq.js +0 -122
- package/dist/PsdViewer-CwiVENmq.js.map +0 -1
- package/dist/TiffViewer-CpewSVmQ.js +0 -142
- package/dist/TiffViewer-CpewSVmQ.js.map +0 -1
- package/dist/Viewer3D-CIrOn63I.js.map +0 -1
- package/dist/index-Dx0IbBcI.js +0 -26831
- package/dist/index-Dx0IbBcI.js.map +0 -1
- package/dist/useViewerFetch-czqbd2Lj.js +0 -25
- package/dist/useViewerFetch-czqbd2Lj.js.map +0 -1
|
@@ -1,30 +1,31 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/**
|
|
3
|
-
* ConnectionsPanel —
|
|
3
|
+
* ConnectionsPanel — how to connect your computer to this server.
|
|
4
4
|
*
|
|
5
5
|
* ⚠⚠ This component is the reason the feature exists ONCE. The desktop
|
|
6
6
|
* app, the web app and any embed mount this same file; none of them owns a
|
|
7
|
-
* hand-written copy of the
|
|
7
|
+
* hand-written copy of the credential panels or the instructions. A fix here
|
|
8
8
|
* lands everywhere on the next release of the package, which is precisely
|
|
9
9
|
* the standing rule ("never write surface-specific behaviour") applied to a
|
|
10
10
|
* feature that was asked for on three surfaces at once.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
12
|
+
* ONE question, answered for the protocol you pick: "connect my computer to
|
|
13
|
+
* filex". Generated from the live deployment — the real host, the storage
|
|
14
|
+
* name, the caller's own username — with the credential each protocol needs
|
|
15
|
+
* minted right above the commands that use it, and a copy button.
|
|
13
16
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* whose every submit 403s would be the dishonest alternative.
|
|
17
|
+
* ⚠ There is NO storage form here, and there has not been one since v0.43.0.
|
|
18
|
+
* The owner, testing the release: "nasıl bağlanılır kısmında ve adminde
|
|
19
|
+
* bağlantılar sayfası … ikisinde de depolar gözüküyor bu depolar sekmesine
|
|
20
|
+
* hiç ihtiyaç yok. kaldıralım." Managing storages is a console job and it
|
|
21
|
+
* already had a console: Admin → Storages (create, edit, delete, plus sync
|
|
22
|
+
* mode, RBAC, drift — everything this panel's half never had). Browsing the
|
|
23
|
+
* storages you can see is the explorer's navigation panel. A second, poorer
|
|
24
|
+
* copy of the first in the screen that answers the OTHER question was the
|
|
25
|
+
* whole complaint; do not bring it back, extend Admin → Storages instead.
|
|
24
26
|
*/
|
|
25
27
|
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
|
26
28
|
import type { ExplorerConfig, LocaleCode } from '../types/ExplorerConfig';
|
|
27
|
-
import type { StorageRow } from '../types/Connections';
|
|
28
29
|
import { useLocale } from '../composables/useLocale';
|
|
29
30
|
import { actionIconSvg } from '../lib/actionIcons'; /* ikon:emoji */
|
|
30
31
|
import { useConnections, connectionsOrigin } from '../composables/useConnections';
|
|
@@ -35,7 +36,6 @@ import {
|
|
|
35
36
|
hostOf,
|
|
36
37
|
type ProtocolGuide,
|
|
37
38
|
} from '../lib/connectionGuides';
|
|
38
|
-
import StorageFields from './StorageFields.vue';
|
|
39
39
|
import ConnectionGuideView from './ConnectionGuideView.vue';
|
|
40
40
|
import S3KeysPanel from './S3KeysPanel.vue';
|
|
41
41
|
import SSHKeysPanel from './SSHKeysPanel.vue';
|
|
@@ -45,15 +45,16 @@ import { resolveLocale } from '../locales/resolve';
|
|
|
45
45
|
|
|
46
46
|
const props = defineProps<{
|
|
47
47
|
config: ExplorerConfig;
|
|
48
|
-
/** Which half to open on. */
|
|
49
|
-
initialTab?: 'storages' | 'connect';
|
|
50
48
|
/** Draw a close control — the desktop app opens this as a full surface
|
|
51
49
|
* and needs a way out; a page-embedded copy has the page's own chrome. */
|
|
52
50
|
closable?: boolean;
|
|
53
51
|
}>();
|
|
54
52
|
|
|
53
|
+
/* ⚠ No `changed`. It existed for one reason — a storage added or removed in
|
|
54
|
+
this panel had to reach the host's own list — and this panel no longer
|
|
55
|
+
adds or removes one. An event that can never fire is worse than none: every
|
|
56
|
+
host wires a handler for it and then trusts a refresh that never comes. */
|
|
55
57
|
const emit = defineEmits<{
|
|
56
|
-
(e: 'changed'): void;
|
|
57
58
|
(e: 'close'): void;
|
|
58
59
|
(e: 'error', err: { message: string }): void;
|
|
59
60
|
}>();
|
|
@@ -64,27 +65,7 @@ const { t } = useLocale(locale);
|
|
|
64
65
|
// Destructured on purpose: Vue only auto-unwraps refs that are top-level in
|
|
65
66
|
// the setup scope, so `conn.storages` inside a template would render a Ref
|
|
66
67
|
// object rather than its value.
|
|
67
|
-
const {
|
|
68
|
-
drivers,
|
|
69
|
-
storages,
|
|
70
|
-
visible,
|
|
71
|
-
me,
|
|
72
|
-
publicUrl,
|
|
73
|
-
loading,
|
|
74
|
-
loaded,
|
|
75
|
-
error,
|
|
76
|
-
canManage,
|
|
77
|
-
denial,
|
|
78
|
-
load,
|
|
79
|
-
createStorage,
|
|
80
|
-
updateStorage,
|
|
81
|
-
deleteStorage,
|
|
82
|
-
testStorage,
|
|
83
|
-
descriptor,
|
|
84
|
-
fields,
|
|
85
|
-
defaults,
|
|
86
|
-
missingRequired,
|
|
87
|
-
} = useConnections(props.config);
|
|
68
|
+
const { visible, me, publicUrl, error, load } = useConnections(props.config);
|
|
88
69
|
|
|
89
70
|
// ── theme ────────────────────────────────────────────────────────────
|
|
90
71
|
// Resolved in JS rather than left to `prefers-color-scheme`, because the
|
|
@@ -104,177 +85,15 @@ const themeResolved = computed(() => {
|
|
|
104
85
|
return osDark.value ? 'dark' : 'light';
|
|
105
86
|
});
|
|
106
87
|
|
|
107
|
-
// ──
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
type FormMode = { kind: 'none' } | { kind: 'new' } | { kind: 'edit'; row: StorageRow };
|
|
112
|
-
const form = ref<FormMode>({ kind: 'none' });
|
|
113
|
-
const fName = ref('');
|
|
114
|
-
const fDriver = ref('');
|
|
115
|
-
const fReadOnly = ref(false);
|
|
116
|
-
const fEnabled = ref(true);
|
|
117
|
-
const fConfig = ref<Record<string, unknown>>({});
|
|
118
|
-
const fInvalid = ref<string[]>([]);
|
|
119
|
-
const saving = ref(false);
|
|
120
|
-
const testing = ref(false);
|
|
121
|
-
const testResult = ref<{ ok: boolean; error?: string; object_count?: number } | null>(null);
|
|
88
|
+
// ── outward: the guides ──────────────────────────────────────────────
|
|
89
|
+
const protocols = guideProtocols();
|
|
90
|
+
const protocol = ref(protocols[0] ?? 'webdav');
|
|
91
|
+
const guideStorage = ref<string>('');
|
|
122
92
|
|
|
123
93
|
/** What the NFS panel published: where to mount, and the path just minted. */
|
|
124
94
|
const nfs = ref<{ host: string; port: number; enabled: boolean; path?: string; readOnly: boolean } | null>(
|
|
125
95
|
null,
|
|
126
96
|
);
|
|
127
|
-
const formError = ref<string | null>(null);
|
|
128
|
-
const confirmDelete = ref<number | null>(null);
|
|
129
|
-
|
|
130
|
-
const driverOptions = computed(() =>
|
|
131
|
-
drivers.value.map((d) => {
|
|
132
|
-
const translated = t(d.i18n_key);
|
|
133
|
-
return { value: d.driver, label: translated === d.i18n_key ? d.label : translated };
|
|
134
|
-
}),
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
const formFields = computed(() => fields(fDriver.value));
|
|
138
|
-
|
|
139
|
-
function openNew() {
|
|
140
|
-
const first = drivers.value[0]?.driver ?? '';
|
|
141
|
-
fDriver.value = first;
|
|
142
|
-
fName.value = '';
|
|
143
|
-
fReadOnly.value = false;
|
|
144
|
-
fEnabled.value = true;
|
|
145
|
-
fConfig.value = defaults(first);
|
|
146
|
-
fInvalid.value = [];
|
|
147
|
-
testResult.value = null;
|
|
148
|
-
formError.value = null;
|
|
149
|
-
form.value = { kind: 'new' };
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function openEdit(row: StorageRow) {
|
|
153
|
-
fDriver.value = row.driver;
|
|
154
|
-
fName.value = row.name;
|
|
155
|
-
fReadOnly.value = !!row.read_only;
|
|
156
|
-
fEnabled.value = row.enabled !== false;
|
|
157
|
-
// A copy: editing must not mutate the list row under the user while
|
|
158
|
-
// they type, and cancelling must actually cancel.
|
|
159
|
-
fConfig.value = { ...(row.config ?? {}) };
|
|
160
|
-
fInvalid.value = [];
|
|
161
|
-
testResult.value = null;
|
|
162
|
-
formError.value = null;
|
|
163
|
-
form.value = { kind: 'edit', row };
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function closeForm() {
|
|
167
|
-
form.value = { kind: 'none' };
|
|
168
|
-
testResult.value = null;
|
|
169
|
-
formError.value = null;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function onDriverChange(next: string) {
|
|
173
|
-
fDriver.value = next;
|
|
174
|
-
// Descriptor defaults, wholesale. Carrying keys over from the previous
|
|
175
|
-
// driver is how a config ends up with fields nothing reads.
|
|
176
|
-
fConfig.value = defaults(next);
|
|
177
|
-
fInvalid.value = [];
|
|
178
|
-
testResult.value = null;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function validate(): boolean {
|
|
182
|
-
const missing = missingRequired(fDriver.value, fConfig.value).map((f) => f.key);
|
|
183
|
-
fInvalid.value = missing;
|
|
184
|
-
if (!fName.value.trim()) formError.value = t('conn.form.nameRequired');
|
|
185
|
-
else if (missing.length) formError.value = t('conn.form.fillRequired');
|
|
186
|
-
else formError.value = null;
|
|
187
|
-
return !formError.value;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
async function runTest() {
|
|
191
|
-
testing.value = true;
|
|
192
|
-
testResult.value = null;
|
|
193
|
-
try {
|
|
194
|
-
testResult.value = await testStorage({
|
|
195
|
-
driver: fDriver.value,
|
|
196
|
-
config: fConfig.value,
|
|
197
|
-
});
|
|
198
|
-
} finally {
|
|
199
|
-
testing.value = false;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
async function save() {
|
|
204
|
-
if (!validate()) return;
|
|
205
|
-
saving.value = true;
|
|
206
|
-
formError.value = null;
|
|
207
|
-
try {
|
|
208
|
-
const body = {
|
|
209
|
-
name: fName.value.trim(),
|
|
210
|
-
driver: fDriver.value,
|
|
211
|
-
config: fConfig.value,
|
|
212
|
-
read_only: fReadOnly.value,
|
|
213
|
-
enabled: fEnabled.value,
|
|
214
|
-
};
|
|
215
|
-
if (form.value.kind === 'edit') await updateStorage(form.value.row.id, body);
|
|
216
|
-
else await createStorage(body);
|
|
217
|
-
closeForm();
|
|
218
|
-
emit('changed');
|
|
219
|
-
} catch (e) {
|
|
220
|
-
const msg = (e as { detail?: string; message?: string }) ?? {};
|
|
221
|
-
// The backend's own words beat a generic status line: "400" says
|
|
222
|
-
// nothing, `ROOT_PATH_FORBIDDEN` says exactly which field is wrong.
|
|
223
|
-
let detail = '';
|
|
224
|
-
try {
|
|
225
|
-
detail = msg.detail ? (JSON.parse(msg.detail) as { error?: string }).error ?? '' : '';
|
|
226
|
-
} catch {
|
|
227
|
-
detail = msg.detail ?? '';
|
|
228
|
-
}
|
|
229
|
-
formError.value = detail || msg.message || String(e);
|
|
230
|
-
emit('error', { message: formError.value });
|
|
231
|
-
} finally {
|
|
232
|
-
saving.value = false;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
async function remove(row: StorageRow) {
|
|
237
|
-
if (confirmDelete.value !== row.id) {
|
|
238
|
-
confirmDelete.value = row.id;
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
confirmDelete.value = null;
|
|
242
|
-
try {
|
|
243
|
-
await deleteStorage(row.id);
|
|
244
|
-
emit('changed');
|
|
245
|
-
} catch (e) {
|
|
246
|
-
emit('error', { message: (e as Error).message });
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/** The one line under a storage's name: where it actually points. */
|
|
251
|
-
function summaryOf(row: StorageRow): string {
|
|
252
|
-
const d = descriptor(row.driver);
|
|
253
|
-
const cfg = row.config ?? {};
|
|
254
|
-
const parts: string[] = [];
|
|
255
|
-
const rootField = d?.fields.find((f) => f.root);
|
|
256
|
-
const pick = (key: string, aliases: string[] = []): string => {
|
|
257
|
-
for (const k of [key, ...aliases]) {
|
|
258
|
-
const v = cfg[k];
|
|
259
|
-
if (typeof v === 'string' && v.trim()) return v;
|
|
260
|
-
}
|
|
261
|
-
return '';
|
|
262
|
-
};
|
|
263
|
-
const host = pick('endpoint') || pick('url') || pick('host');
|
|
264
|
-
if (host) parts.push(host);
|
|
265
|
-
const bucket = pick('bucket');
|
|
266
|
-
if (bucket) parts.push(bucket);
|
|
267
|
-
if (rootField) {
|
|
268
|
-
const r = pick(rootField.key, rootField.aliases ?? []);
|
|
269
|
-
if (r) parts.push(r);
|
|
270
|
-
}
|
|
271
|
-
return parts.join(' · ');
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// ── outward: the guides ──────────────────────────────────────────────
|
|
275
|
-
const protocols = guideProtocols();
|
|
276
|
-
const protocol = ref(protocols[0] ?? 'webdav');
|
|
277
|
-
const guideStorage = ref<string>('');
|
|
278
97
|
|
|
279
98
|
const origin = computed(() => connectionsOrigin(props.config, publicUrl.value));
|
|
280
99
|
|
|
@@ -361,7 +180,6 @@ onBeforeUnmount(() => mq?.removeEventListener?.('change', onMq));
|
|
|
361
180
|
watch(
|
|
362
181
|
() => [props.config.apiBase, props.config.endpoint],
|
|
363
182
|
() => {
|
|
364
|
-
closeForm();
|
|
365
183
|
void load();
|
|
366
184
|
},
|
|
367
185
|
);
|
|
@@ -394,233 +212,13 @@ watch(
|
|
|
394
212
|
</button>
|
|
395
213
|
</header>
|
|
396
214
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
:class="{ 'is-active': tab === 'storages' }"
|
|
403
|
-
:aria-selected="tab === 'storages'"
|
|
404
|
-
data-testid="tab-storages"
|
|
405
|
-
@click="tab = 'storages'"
|
|
406
|
-
>
|
|
407
|
-
{{ t('conn.tab.storages') }}
|
|
408
|
-
</button>
|
|
409
|
-
<button
|
|
410
|
-
type="button"
|
|
411
|
-
role="tab"
|
|
412
|
-
class="fe-conn__tab"
|
|
413
|
-
:class="{ 'is-active': tab === 'connect' }"
|
|
414
|
-
:aria-selected="tab === 'connect'"
|
|
415
|
-
data-testid="tab-connect"
|
|
416
|
-
@click="tab = 'connect'"
|
|
417
|
-
>
|
|
418
|
-
{{ t('conn.tab.connect') }}
|
|
419
|
-
</button>
|
|
420
|
-
</nav>
|
|
421
|
-
|
|
422
|
-
<!-- ══ inward ════════════════════════════════════════════════ -->
|
|
423
|
-
<section v-if="tab === 'storages'" class="fe-conn__body" role="tabpanel">
|
|
424
|
-
<p v-if="loading && !loaded" class="fe-conn__muted">
|
|
425
|
-
{{ t('conn.loading') }}
|
|
426
|
-
</p>
|
|
427
|
-
|
|
428
|
-
<template v-else>
|
|
429
|
-
<!-- The honest non-admin state. Not a disabled form: a form you
|
|
430
|
-
cannot submit teaches the wrong thing about whose install this
|
|
431
|
-
is. -->
|
|
432
|
-
<div
|
|
433
|
-
v-if="canManage === false"
|
|
434
|
-
class="fe-conn__card fe-conn__card--notice"
|
|
435
|
-
data-testid="no-admin"
|
|
436
|
-
>
|
|
437
|
-
<strong>{{ t('conn.denied.title') }}</strong>
|
|
438
|
-
<p class="fe-conn__muted">
|
|
439
|
-
{{
|
|
440
|
-
denial === 'anonymous'
|
|
441
|
-
? t('conn.denied.anonymous')
|
|
442
|
-
: denial === 'unreachable'
|
|
443
|
-
? t('conn.denied.unreachable', { error: error ?? '' })
|
|
444
|
-
: t('conn.denied.none')
|
|
445
|
-
}}
|
|
446
|
-
</p>
|
|
447
|
-
<p class="fe-conn__muted">{{ t('conn.denied.guideHint') }}</p>
|
|
448
|
-
<button type="button" class="fe-conn__btn fe-conn__btn--primary" @click="tab = 'connect'">
|
|
449
|
-
{{ t('conn.denied.guideCta') }}
|
|
450
|
-
</button>
|
|
451
|
-
</div>
|
|
452
|
-
|
|
453
|
-
<!-- What they CAN see, even without admin: the storages they may
|
|
454
|
-
browse. Otherwise this half of the panel is blank for them. -->
|
|
455
|
-
<div v-if="canManage === false && visible.length" class="fe-conn__list">
|
|
456
|
-
<div v-for="name in visible" :key="name" class="fe-conn__card">
|
|
457
|
-
<div class="fe-conn__rowmain">
|
|
458
|
-
<strong class="fe-conn__name">{{ name }}</strong>
|
|
459
|
-
<span class="fe-conn__muted">{{ t('conn.visibleOnly') }}</span>
|
|
460
|
-
</div>
|
|
461
|
-
</div>
|
|
462
|
-
</div>
|
|
463
|
-
|
|
464
|
-
<template v-if="canManage">
|
|
465
|
-
<p v-if="error" class="fe-conn__error">{{ error }}</p>
|
|
466
|
-
|
|
467
|
-
<!-- ── the form ── -->
|
|
468
|
-
<div v-if="form.kind !== 'none'" class="fe-conn__card fe-conn__form" data-testid="storage-form">
|
|
469
|
-
<h3 class="fe-conn__formtitle">
|
|
470
|
-
{{ form.kind === 'edit' ? t('conn.form.editTitle', { name: fName }) : t('conn.form.newTitle') }}
|
|
471
|
-
</h3>
|
|
472
|
-
|
|
473
|
-
<div class="fe-cfield__row">
|
|
474
|
-
<label class="fe-cfield__label" for="fe-conn-name">
|
|
475
|
-
{{ t('conn.form.name') }}<span class="fe-cfield__req">*</span>
|
|
476
|
-
</label>
|
|
477
|
-
<input
|
|
478
|
-
id="fe-conn-name"
|
|
479
|
-
v-model="fName"
|
|
480
|
-
class="fe-cfield__input"
|
|
481
|
-
data-testid="storage-name"
|
|
482
|
-
:placeholder="t('conn.form.namePlaceholder')"
|
|
483
|
-
/>
|
|
484
|
-
<p class="fe-cfield__help">{{ t('conn.form.nameHelp') }}</p>
|
|
485
|
-
</div>
|
|
486
|
-
|
|
487
|
-
<div class="fe-cfield__row">
|
|
488
|
-
<label class="fe-cfield__label" for="fe-conn-driver">{{ t('conn.form.driver') }}</label>
|
|
489
|
-
<select
|
|
490
|
-
id="fe-conn-driver"
|
|
491
|
-
class="fe-cfield__input"
|
|
492
|
-
data-testid="storage-driver"
|
|
493
|
-
:value="fDriver"
|
|
494
|
-
:disabled="form.kind === 'edit'"
|
|
495
|
-
@change="onDriverChange(($event.target as HTMLSelectElement).value)"
|
|
496
|
-
>
|
|
497
|
-
<option v-for="o in driverOptions" :key="o.value" :value="o.value">
|
|
498
|
-
{{ o.label }}
|
|
499
|
-
</option>
|
|
500
|
-
</select>
|
|
501
|
-
<p v-if="form.kind === 'edit'" class="fe-cfield__help">
|
|
502
|
-
{{ t('conn.form.driverLocked') }}
|
|
503
|
-
</p>
|
|
504
|
-
</div>
|
|
505
|
-
|
|
506
|
-
<StorageFields
|
|
507
|
-
v-model="fConfig"
|
|
508
|
-
:fields="formFields"
|
|
509
|
-
:locale="locale"
|
|
510
|
-
:invalid="fInvalid"
|
|
511
|
-
/>
|
|
512
|
-
|
|
513
|
-
<label class="fe-cfield__check">
|
|
514
|
-
<input v-model="fReadOnly" type="checkbox" data-testid="storage-readonly" />
|
|
515
|
-
<span>{{ t('conn.form.readOnly') }}</span>
|
|
516
|
-
</label>
|
|
517
|
-
<label class="fe-cfield__check">
|
|
518
|
-
<input v-model="fEnabled" type="checkbox" />
|
|
519
|
-
<span>{{ t('conn.form.enabled') }}</span>
|
|
520
|
-
</label>
|
|
215
|
+
<!-- ⚠ The server's own words when the storage list cannot be fetched. It
|
|
216
|
+
used to be printed by the storage half, which is gone; the guides
|
|
217
|
+
below are built from that same list, so swallowing the failure here
|
|
218
|
+
would leave a page of instructions quietly missing its storages. -->
|
|
219
|
+
<p v-if="error" class="fe-conn__error" data-testid="conn-error">{{ error }}</p>
|
|
521
220
|
|
|
522
|
-
|
|
523
|
-
<p
|
|
524
|
-
v-if="testResult"
|
|
525
|
-
class="fe-conn__testresult"
|
|
526
|
-
:class="testResult.ok ? 'is-ok' : 'is-bad'"
|
|
527
|
-
data-testid="test-result"
|
|
528
|
-
>
|
|
529
|
-
{{
|
|
530
|
-
testResult.ok
|
|
531
|
-
? t('conn.form.testOk', { count: testResult.object_count ?? 0 })
|
|
532
|
-
: t('conn.form.testFail', { error: testResult.error ?? '' })
|
|
533
|
-
}}
|
|
534
|
-
</p>
|
|
535
|
-
|
|
536
|
-
<div class="fe-conn__actions">
|
|
537
|
-
<button
|
|
538
|
-
type="button"
|
|
539
|
-
class="fe-conn__btn"
|
|
540
|
-
data-testid="storage-test"
|
|
541
|
-
:disabled="testing"
|
|
542
|
-
@click="runTest"
|
|
543
|
-
>
|
|
544
|
-
{{ testing ? t('conn.form.testing') : t('conn.form.test') }}
|
|
545
|
-
</button>
|
|
546
|
-
<button
|
|
547
|
-
type="button"
|
|
548
|
-
class="fe-conn__btn fe-conn__btn--primary"
|
|
549
|
-
data-testid="storage-save"
|
|
550
|
-
:disabled="saving"
|
|
551
|
-
@click="save"
|
|
552
|
-
>
|
|
553
|
-
{{ saving ? t('conn.form.saving') : t('conn.form.save') }}
|
|
554
|
-
</button>
|
|
555
|
-
<button type="button" class="fe-conn__btn" @click="closeForm">
|
|
556
|
-
{{ t('conn.form.cancel') }}
|
|
557
|
-
</button>
|
|
558
|
-
</div>
|
|
559
|
-
</div>
|
|
560
|
-
|
|
561
|
-
<!-- ── the list ── -->
|
|
562
|
-
<div v-else>
|
|
563
|
-
<div class="fe-conn__listhead">
|
|
564
|
-
<span class="fe-conn__muted">
|
|
565
|
-
{{ t('conn.list.count', { n: storages.length }) }}
|
|
566
|
-
</span>
|
|
567
|
-
<button
|
|
568
|
-
type="button"
|
|
569
|
-
class="fe-conn__btn fe-conn__btn--primary"
|
|
570
|
-
data-testid="storage-add"
|
|
571
|
-
@click="openNew"
|
|
572
|
-
>
|
|
573
|
-
+ {{ t('conn.list.add') }}
|
|
574
|
-
</button>
|
|
575
|
-
</div>
|
|
576
|
-
|
|
577
|
-
<p v-if="!storages.length" class="fe-conn__empty">
|
|
578
|
-
{{ t('conn.list.empty') }}
|
|
579
|
-
</p>
|
|
580
|
-
|
|
581
|
-
<div class="fe-conn__list" data-testid="storage-list">
|
|
582
|
-
<div v-for="row in storages" :key="row.id" class="fe-conn__card">
|
|
583
|
-
<div class="fe-conn__rowmain">
|
|
584
|
-
<div class="fe-conn__rowtext">
|
|
585
|
-
<strong class="fe-conn__name">{{ row.name }}</strong>
|
|
586
|
-
<span class="fe-conn__badge">{{ row.driver }}</span>
|
|
587
|
-
<span v-if="row.read_only" class="fe-conn__badge fe-conn__badge--warn">
|
|
588
|
-
{{ t('conn.list.readOnly') }}
|
|
589
|
-
</span>
|
|
590
|
-
<span v-if="row.enabled === false" class="fe-conn__badge">
|
|
591
|
-
{{ t('conn.list.disabled') }}
|
|
592
|
-
</span>
|
|
593
|
-
<div v-if="summaryOf(row)" class="fe-conn__muted fe-conn__summary">
|
|
594
|
-
{{ summaryOf(row) }}
|
|
595
|
-
</div>
|
|
596
|
-
</div>
|
|
597
|
-
<div class="fe-conn__rowbtns">
|
|
598
|
-
<button
|
|
599
|
-
type="button"
|
|
600
|
-
class="fe-conn__btn"
|
|
601
|
-
:data-testid="`storage-edit-${row.name}`"
|
|
602
|
-
@click="openEdit(row)"
|
|
603
|
-
>
|
|
604
|
-
{{ t('conn.list.edit') }}
|
|
605
|
-
</button>
|
|
606
|
-
<button
|
|
607
|
-
type="button"
|
|
608
|
-
class="fe-conn__btn fe-conn__btn--danger"
|
|
609
|
-
@click="remove(row)"
|
|
610
|
-
>
|
|
611
|
-
{{ confirmDelete === row.id ? t('conn.list.confirm') : t('conn.list.remove') }}
|
|
612
|
-
</button>
|
|
613
|
-
</div>
|
|
614
|
-
</div>
|
|
615
|
-
</div>
|
|
616
|
-
</div>
|
|
617
|
-
</div>
|
|
618
|
-
</template>
|
|
619
|
-
</template>
|
|
620
|
-
</section>
|
|
621
|
-
|
|
622
|
-
<!-- ══ outward ═══════════════════════════════════════════════ -->
|
|
623
|
-
<section v-else class="fe-conn__body" role="tabpanel">
|
|
221
|
+
<section class="fe-conn__body">
|
|
624
222
|
<div class="fe-conn__guidebar">
|
|
625
223
|
<label v-if="protocols.length > 1" class="fe-conn__pick">
|
|
626
224
|
<span class="fe-cfield__label">{{ t('conn.guide.protocol') }}</span>
|
|
@@ -747,146 +345,18 @@ watch(
|
|
|
747
345
|
font-size: 13px;
|
|
748
346
|
overflow-wrap: anywhere;
|
|
749
347
|
}
|
|
750
|
-
.fe-conn__tabs {
|
|
751
|
-
display: flex;
|
|
752
|
-
gap: 4px;
|
|
753
|
-
border-bottom: 1px solid var(--fe-border);
|
|
754
|
-
}
|
|
755
|
-
.fe-conn__tab {
|
|
756
|
-
font: inherit;
|
|
757
|
-
font-size: 13.5px;
|
|
758
|
-
border: 0;
|
|
759
|
-
background: none;
|
|
760
|
-
color: var(--fe-text-muted);
|
|
761
|
-
padding: 7px 12px;
|
|
762
|
-
cursor: pointer;
|
|
763
|
-
border-bottom: 2px solid transparent;
|
|
764
|
-
}
|
|
765
|
-
.fe-conn__tab:hover {
|
|
766
|
-
color: var(--fe-text);
|
|
767
|
-
}
|
|
768
|
-
.fe-conn__tab.is-active {
|
|
769
|
-
color: var(--fe-primary);
|
|
770
|
-
border-bottom-color: var(--fe-primary);
|
|
771
|
-
font-weight: 600;
|
|
772
|
-
}
|
|
773
348
|
.fe-conn__body {
|
|
774
349
|
display: flex;
|
|
775
350
|
flex-direction: column;
|
|
776
351
|
gap: 12px;
|
|
777
352
|
min-width: 0;
|
|
778
353
|
}
|
|
779
|
-
.fe-conn__card {
|
|
780
|
-
border: 1px solid var(--fe-border);
|
|
781
|
-
border-radius: var(--fe-radius);
|
|
782
|
-
background: var(--fe-bg-elev);
|
|
783
|
-
padding: 12px 14px;
|
|
784
|
-
}
|
|
785
|
-
.fe-conn__card--notice {
|
|
786
|
-
display: flex;
|
|
787
|
-
flex-direction: column;
|
|
788
|
-
gap: 8px;
|
|
789
|
-
align-items: flex-start;
|
|
790
|
-
}
|
|
791
|
-
.fe-conn__form {
|
|
792
|
-
display: flex;
|
|
793
|
-
flex-direction: column;
|
|
794
|
-
gap: 14px;
|
|
795
|
-
}
|
|
796
|
-
.fe-conn__formtitle {
|
|
797
|
-
margin: 0;
|
|
798
|
-
font-size: 14px;
|
|
799
|
-
font-weight: 650;
|
|
800
|
-
}
|
|
801
|
-
.fe-conn__list {
|
|
802
|
-
display: flex;
|
|
803
|
-
flex-direction: column;
|
|
804
|
-
gap: 8px;
|
|
805
|
-
}
|
|
806
|
-
.fe-conn__listhead {
|
|
807
|
-
display: flex;
|
|
808
|
-
align-items: center;
|
|
809
|
-
justify-content: space-between;
|
|
810
|
-
gap: 12px;
|
|
811
|
-
margin-bottom: 4px;
|
|
812
|
-
}
|
|
813
|
-
.fe-conn__rowmain {
|
|
814
|
-
display: flex;
|
|
815
|
-
align-items: flex-start;
|
|
816
|
-
justify-content: space-between;
|
|
817
|
-
gap: 12px;
|
|
818
|
-
flex-wrap: wrap;
|
|
819
|
-
}
|
|
820
|
-
.fe-conn__rowtext {
|
|
821
|
-
min-width: 0;
|
|
822
|
-
display: flex;
|
|
823
|
-
align-items: center;
|
|
824
|
-
gap: 8px;
|
|
825
|
-
flex-wrap: wrap;
|
|
826
|
-
}
|
|
827
|
-
.fe-conn__rowbtns {
|
|
828
|
-
display: flex;
|
|
829
|
-
gap: 6px;
|
|
830
|
-
flex: 0 0 auto;
|
|
831
|
-
}
|
|
832
|
-
.fe-conn__name {
|
|
833
|
-
font-size: 14px;
|
|
834
|
-
}
|
|
835
|
-
.fe-conn__summary {
|
|
836
|
-
flex-basis: 100%;
|
|
837
|
-
font-family: var(--fe-font-mono);
|
|
838
|
-
font-size: 12px;
|
|
839
|
-
overflow-wrap: anywhere;
|
|
840
|
-
}
|
|
841
|
-
.fe-conn__badge {
|
|
842
|
-
font-size: 11px;
|
|
843
|
-
text-transform: uppercase;
|
|
844
|
-
letter-spacing: 0.04em;
|
|
845
|
-
border: 1px solid var(--fe-border-strong);
|
|
846
|
-
border-radius: 999px;
|
|
847
|
-
padding: 1px 8px;
|
|
848
|
-
color: var(--fe-text-muted);
|
|
849
|
-
}
|
|
850
|
-
.fe-conn__badge--warn {
|
|
851
|
-
color: var(--fe-danger);
|
|
852
|
-
border-color: var(--fe-danger);
|
|
853
|
-
}
|
|
854
|
-
.fe-conn__muted {
|
|
855
|
-
color: var(--fe-text-muted);
|
|
856
|
-
font-size: 12.5px;
|
|
857
|
-
margin: 0;
|
|
858
|
-
line-height: 1.5;
|
|
859
|
-
}
|
|
860
|
-
.fe-conn__empty {
|
|
861
|
-
border: 1px dashed var(--fe-border-strong);
|
|
862
|
-
border-radius: var(--fe-radius);
|
|
863
|
-
padding: 16px;
|
|
864
|
-
color: var(--fe-text-muted);
|
|
865
|
-
font-size: 13px;
|
|
866
|
-
margin: 0;
|
|
867
|
-
}
|
|
868
354
|
.fe-conn__error {
|
|
869
355
|
margin: 0;
|
|
870
356
|
color: var(--fe-danger);
|
|
871
357
|
font-size: 13px;
|
|
872
358
|
overflow-wrap: anywhere;
|
|
873
359
|
}
|
|
874
|
-
.fe-conn__testresult {
|
|
875
|
-
margin: 0;
|
|
876
|
-
font-size: 13px;
|
|
877
|
-
overflow-wrap: anywhere;
|
|
878
|
-
}
|
|
879
|
-
.fe-conn__testresult.is-ok {
|
|
880
|
-
color: #16a34a;
|
|
881
|
-
}
|
|
882
|
-
.fe-conn__testresult.is-bad {
|
|
883
|
-
color: var(--fe-danger);
|
|
884
|
-
}
|
|
885
|
-
.fe-conn__actions {
|
|
886
|
-
display: flex;
|
|
887
|
-
gap: 8px;
|
|
888
|
-
flex-wrap: wrap;
|
|
889
|
-
}
|
|
890
360
|
.fe-conn__btn {
|
|
891
361
|
font: inherit;
|
|
892
362
|
font-size: 13px;
|
|
@@ -904,14 +374,6 @@ watch(
|
|
|
904
374
|
opacity: 0.55;
|
|
905
375
|
cursor: default;
|
|
906
376
|
}
|
|
907
|
-
.fe-conn__btn--primary {
|
|
908
|
-
background: var(--fe-primary);
|
|
909
|
-
border-color: var(--fe-primary);
|
|
910
|
-
color: #fff;
|
|
911
|
-
}
|
|
912
|
-
.fe-conn__btn--danger {
|
|
913
|
-
color: var(--fe-danger);
|
|
914
|
-
}
|
|
915
377
|
.fe-conn__guidebar {
|
|
916
378
|
display: flex;
|
|
917
379
|
gap: 12px;
|