@fleetbase/solid-engine 0.0.3 → 0.0.5
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/ACL_SOLUTION.md +72 -0
- package/CSS_SCOPE_ISSUE.md +140 -0
- package/HOTFIX_SYNTAX_ERROR.md +100 -0
- package/LICENSE.md +651 -21
- package/MANUAL_ACL_SETUP.md +135 -0
- package/README.md +74 -27
- package/REFACTORING_SUMMARY.md +330 -0
- package/VERIFICATION_CHECKLIST.md +82 -0
- package/addon/components/modals/create-solid-folder.hbs +29 -0
- package/addon/components/modals/import-solid-resources.hbs +85 -0
- package/addon/controllers/data/content.js +17 -0
- package/addon/controllers/data/index.js +219 -0
- package/addon/controllers/home.js +84 -0
- package/addon/engine.js +1 -24
- package/addon/extension.js +26 -0
- package/addon/routes/data/content.js +11 -0
- package/addon/routes/data/index.js +17 -0
- package/addon/routes.js +2 -7
- package/addon/styles/solid-engine.css +1 -2
- package/addon/templates/account.hbs +3 -3
- package/addon/templates/application.hbs +2 -12
- package/addon/templates/data/content.hbs +48 -0
- package/addon/templates/{pods/explorer.hbs → data/index.hbs} +6 -5
- package/addon/templates/home.hbs +168 -10
- package/app/components/modals/{backup-pod.js → create-solid-folder.js} +1 -1
- package/app/components/modals/{resync-pod.js → import-solid-resources.js} +1 -1
- package/app/components/modals/{create-pod.js → setup-css-credentials.js} +1 -1
- package/composer.json +5 -11
- package/extension.json +2 -2
- package/index.js +0 -11
- package/package.json +9 -9
- package/server/migrations/2024_12_21_add_css_credentials_to_solid_identities_table.php +32 -0
- package/server/src/Client/OpenIDConnectClient.php +686 -15
- package/server/src/Client/SolidClient.php +104 -8
- package/server/src/Http/Controllers/DataController.php +261 -0
- package/server/src/Http/Controllers/OIDCController.php +42 -8
- package/server/src/Http/Controllers/SolidController.php +179 -85
- package/server/src/Models/SolidIdentity.php +13 -3
- package/server/src/Services/AclService.php +146 -0
- package/server/src/Services/PodService.php +863 -0
- package/server/src/Services/ResourceSyncService.php +336 -0
- package/server/src/Services/VehicleSyncService.php +289 -0
- package/server/src/Support/Utils.php +10 -0
- package/server/src/routes.php +25 -1
- package/addon/components/modals/backup-pod.hbs +0 -3
- package/addon/components/modals/create-pod.hbs +0 -5
- package/addon/components/modals/resync-pod.hbs +0 -3
- package/addon/controllers/pods/explorer/content.js +0 -12
- package/addon/controllers/pods/explorer.js +0 -149
- package/addon/controllers/pods/index/pod.js +0 -12
- package/addon/controllers/pods/index.js +0 -137
- package/addon/routes/pods/explorer/content.js +0 -10
- package/addon/routes/pods/explorer.js +0 -44
- package/addon/routes/pods/index/pod.js +0 -3
- package/addon/routes/pods/index.js +0 -21
- package/addon/templates/pods/explorer/content.hbs +0 -19
- package/addon/templates/pods/index/pod.hbs +0 -11
- package/addon/templates/pods/index.hbs +0 -19
- package/server/src/LegacyClient/Identity/IdentityProvider.php +0 -174
- package/server/src/LegacyClient/Identity/Profile.php +0 -18
- package/server/src/LegacyClient/OIDCClient.php +0 -350
- package/server/src/LegacyClient/Profile/WebID.php +0 -26
- package/server/src/LegacyClient/SolidClient.php +0 -271
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<Modal::Default
|
|
2
|
+
@modalIsOpened={{@modalIsOpened}}
|
|
3
|
+
@options={{@options}}
|
|
4
|
+
@confirm={{@options.confirm}}
|
|
5
|
+
@decline={{@options.decline}}
|
|
6
|
+
>
|
|
7
|
+
<div class="modal-body-container">
|
|
8
|
+
<div class="space-y-4">
|
|
9
|
+
<InputGroup @name="Folder Name" @wrapperClass="mb-0">
|
|
10
|
+
<Input
|
|
11
|
+
@type="text"
|
|
12
|
+
@value={{@options.folderName}}
|
|
13
|
+
placeholder="Enter folder name (e.g., vehicles, drivers)"
|
|
14
|
+
class="w-full form-input"
|
|
15
|
+
/>
|
|
16
|
+
</InputGroup>
|
|
17
|
+
|
|
18
|
+
<div class="text-sm text-gray-600 dark:text-gray-400">
|
|
19
|
+
<p>This will create a new folder in your Solid pod for organizing your data.</p>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
{{#if @options.error}}
|
|
23
|
+
<div class="bg-red-50 dark:bg-red-900 border border-red-200 dark:border-red-700 rounded p-3">
|
|
24
|
+
<p class="text-sm text-red-800 dark:text-red-200">{{@options.error}}</p>
|
|
25
|
+
</div>
|
|
26
|
+
{{/if}}
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
</Modal::Default>
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<Modal::Default
|
|
2
|
+
@modalIsOpened={{@modalIsOpened}}
|
|
3
|
+
@options={{@options}}
|
|
4
|
+
@confirm={{@options.confirm}}
|
|
5
|
+
@decline={{@options.decline}}
|
|
6
|
+
>
|
|
7
|
+
<div class="modal-body-container">
|
|
8
|
+
<div class="space-y-4">
|
|
9
|
+
<div>
|
|
10
|
+
<p class="text-gray-700 dark:text-gray-300 mb-4">
|
|
11
|
+
Select the Fleetops resources you want to import into your Solid pod:
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<div class="space-y-3">
|
|
15
|
+
<label class="flex items-center p-3 bg-gray-50 dark:bg-gray-800 rounded cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700">
|
|
16
|
+
<input
|
|
17
|
+
type="checkbox"
|
|
18
|
+
checked={{@options.resourceTypes.vehicles}}
|
|
19
|
+
{{on "change" (fn @options.toggleResourceType "vehicles")}}
|
|
20
|
+
class="form-checkbox h-5 w-5 text-blue-600"
|
|
21
|
+
/>
|
|
22
|
+
<div class="ml-3">
|
|
23
|
+
<div class="font-medium text-gray-900 dark:text-white">Vehicles</div>
|
|
24
|
+
<div class="text-sm text-gray-600 dark:text-gray-400">Import all vehicles from Fleetops</div>
|
|
25
|
+
</div>
|
|
26
|
+
</label>
|
|
27
|
+
|
|
28
|
+
<label class="flex items-center p-3 bg-gray-50 dark:bg-gray-800 rounded cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700">
|
|
29
|
+
<input
|
|
30
|
+
type="checkbox"
|
|
31
|
+
checked={{@options.resourceTypes.drivers}}
|
|
32
|
+
{{on "change" (fn @options.toggleResourceType "drivers")}}
|
|
33
|
+
class="form-checkbox h-5 w-5 text-blue-600"
|
|
34
|
+
/>
|
|
35
|
+
<div class="ml-3">
|
|
36
|
+
<div class="font-medium text-gray-900 dark:text-white">Drivers</div>
|
|
37
|
+
<div class="text-sm text-gray-600 dark:text-gray-400">Import all drivers from Fleetops</div>
|
|
38
|
+
</div>
|
|
39
|
+
</label>
|
|
40
|
+
|
|
41
|
+
<label class="flex items-center p-3 bg-gray-50 dark:bg-gray-800 rounded cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700">
|
|
42
|
+
<input
|
|
43
|
+
type="checkbox"
|
|
44
|
+
checked={{@options.resourceTypes.contacts}}
|
|
45
|
+
{{on "change" (fn @options.toggleResourceType "contacts")}}
|
|
46
|
+
class="form-checkbox h-5 w-5 text-blue-600"
|
|
47
|
+
/>
|
|
48
|
+
<div class="ml-3">
|
|
49
|
+
<div class="font-medium text-gray-900 dark:text-white">Contacts</div>
|
|
50
|
+
<div class="text-sm text-gray-600 dark:text-gray-400">Import all contacts/customers from Fleetops</div>
|
|
51
|
+
</div>
|
|
52
|
+
</label>
|
|
53
|
+
|
|
54
|
+
<label class="flex items-center p-3 bg-gray-50 dark:bg-gray-800 rounded cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700">
|
|
55
|
+
<input
|
|
56
|
+
type="checkbox"
|
|
57
|
+
checked={{@options.resourceTypes.orders}}
|
|
58
|
+
{{on "change" (fn @options.toggleResourceType "orders")}}
|
|
59
|
+
class="form-checkbox h-5 w-5 text-blue-600"
|
|
60
|
+
/>
|
|
61
|
+
<div class="ml-3">
|
|
62
|
+
<div class="font-medium text-gray-900 dark:text-white">Orders</div>
|
|
63
|
+
<div class="text-sm text-gray-600 dark:text-gray-400">Import all orders from Fleetops</div>
|
|
64
|
+
</div>
|
|
65
|
+
</label>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
{{#if @options.importProgress}}
|
|
70
|
+
<div class="bg-blue-50 dark:bg-blue-900 border border-blue-200 dark:border-blue-700 rounded p-3">
|
|
71
|
+
<div class="flex items-center">
|
|
72
|
+
<Spinner class="mr-3" />
|
|
73
|
+
<p class="text-sm text-blue-800 dark:text-blue-200">{{@options.importProgress}}</p>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
{{/if}}
|
|
77
|
+
|
|
78
|
+
{{#if @options.error}}
|
|
79
|
+
<div class="bg-red-50 dark:bg-red-900 border border-red-200 dark:border-red-700 rounded p-3">
|
|
80
|
+
<p class="text-sm text-red-800 dark:text-red-200">{{@options.error}}</p>
|
|
81
|
+
</div>
|
|
82
|
+
{{/if}}
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</Modal::Default>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import Controller from '@ember/controller';
|
|
2
|
+
import { action } from '@ember/object';
|
|
3
|
+
import { inject as service } from '@ember/service';
|
|
4
|
+
|
|
5
|
+
export default class DataContentController extends Controller {
|
|
6
|
+
@service hostRouter;
|
|
7
|
+
|
|
8
|
+
@action back() {
|
|
9
|
+
this.hostRouter.transitionTo('console.solid-protocol.data.index');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@action viewFile() {
|
|
13
|
+
if (this.model.url) {
|
|
14
|
+
window.open(this.model.url, '_blank');
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import Controller from '@ember/controller';
|
|
2
|
+
import { action } from '@ember/object';
|
|
3
|
+
import { inject as service } from '@ember/service';
|
|
4
|
+
import { tracked } from '@glimmer/tracking';
|
|
5
|
+
import { task, timeout } from 'ember-concurrency';
|
|
6
|
+
|
|
7
|
+
export default class DataIndexController extends Controller {
|
|
8
|
+
@service hostRouter;
|
|
9
|
+
@service fetch;
|
|
10
|
+
@service notifications;
|
|
11
|
+
@service modalsManager;
|
|
12
|
+
@service crud;
|
|
13
|
+
@tracked query = '';
|
|
14
|
+
queryParams = ['query'];
|
|
15
|
+
columns = [
|
|
16
|
+
{
|
|
17
|
+
label: 'Name',
|
|
18
|
+
valuePath: 'name',
|
|
19
|
+
width: '75%',
|
|
20
|
+
cellComponent: 'table/cell/pod-content-name',
|
|
21
|
+
onClick: this.viewContents,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
label: 'Type',
|
|
25
|
+
valuePath: 'type',
|
|
26
|
+
cellClassNames: 'capitalize',
|
|
27
|
+
width: '5%',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
label: 'Size',
|
|
31
|
+
valuePath: 'size',
|
|
32
|
+
width: '5%',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
label: 'Created At',
|
|
36
|
+
valuePath: 'created_at',
|
|
37
|
+
width: '15%',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
label: '',
|
|
41
|
+
cellComponent: 'table/cell/pod-content-actions',
|
|
42
|
+
ddButtonText: false,
|
|
43
|
+
ddButtonIcon: 'ellipsis-h',
|
|
44
|
+
ddButtonIconPrefix: 'fas',
|
|
45
|
+
ddMenuLabel: 'Actions',
|
|
46
|
+
cellClassNames: 'overflow-visible',
|
|
47
|
+
wrapperClass: 'flex items-center justify-end mx-2',
|
|
48
|
+
width: '10%',
|
|
49
|
+
actions: (content) => {
|
|
50
|
+
return [
|
|
51
|
+
{
|
|
52
|
+
label: content.type === 'folder' ? 'Browse Folder' : 'View Contents',
|
|
53
|
+
fn: this.viewContents,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
separator: true,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
label: 'Delete',
|
|
60
|
+
fn: this.deleteItem,
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
},
|
|
64
|
+
sortable: false,
|
|
65
|
+
filterable: false,
|
|
66
|
+
resizable: false,
|
|
67
|
+
searchable: false,
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
@action reload() {
|
|
72
|
+
this.hostRouter.refresh();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@action back() {
|
|
76
|
+
this.hostRouter.transitionTo('console.solid-protocol.home');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@action viewContents(content) {
|
|
80
|
+
if (content.type === 'folder') {
|
|
81
|
+
return this.hostRouter.transitionTo('console.solid-protocol.data.content', content.slug);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (content.type === 'file') {
|
|
85
|
+
// Open file viewer or download
|
|
86
|
+
window.open(content.url, '_blank');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@action createFolder() {
|
|
91
|
+
this.modalsManager.show('modals/create-solid-folder', {
|
|
92
|
+
title: 'Create New Folder',
|
|
93
|
+
acceptButtonText: 'Create',
|
|
94
|
+
acceptButtonIcon: 'folder-plus',
|
|
95
|
+
folderName: '',
|
|
96
|
+
keepOpen: true,
|
|
97
|
+
confirm: async (modal) => {
|
|
98
|
+
const folderName = modal.getOption('folderName');
|
|
99
|
+
if (!folderName) {
|
|
100
|
+
return this.notifications.warning('Please enter a folder name.');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
modal.startLoading();
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const response = await this.fetch.post(
|
|
107
|
+
'data/folder',
|
|
108
|
+
{
|
|
109
|
+
name: folderName,
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
namespace: 'solid/int/v1',
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
if (response.success) {
|
|
117
|
+
this.notifications.success(`Folder "${folderName}" created successfully!`);
|
|
118
|
+
this.hostRouter.refresh();
|
|
119
|
+
return modal.done();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
this.notifications.error(response.error || 'Failed to create folder.');
|
|
123
|
+
} catch (error) {
|
|
124
|
+
this.notifications.serverError(error);
|
|
125
|
+
} finally {
|
|
126
|
+
modal.stopLoading();
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@action importResources() {
|
|
133
|
+
const resourceTypes = {
|
|
134
|
+
vehicles: false,
|
|
135
|
+
drivers: false,
|
|
136
|
+
contacts: false,
|
|
137
|
+
orders: false,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
this.modalsManager.show('modals/import-solid-resources', {
|
|
141
|
+
title: 'Import Fleetops Resources',
|
|
142
|
+
acceptButtonText: 'Import Selected',
|
|
143
|
+
acceptButtonIcon: 'download',
|
|
144
|
+
resourceTypes,
|
|
145
|
+
importProgress: null,
|
|
146
|
+
toggleResourceType: (type) => {
|
|
147
|
+
resourceTypes[type] = !resourceTypes[type];
|
|
148
|
+
},
|
|
149
|
+
confirm: async (modal) => {
|
|
150
|
+
const selected = Object.keys(resourceTypes).filter((type) => resourceTypes[type]);
|
|
151
|
+
|
|
152
|
+
if (selected.length === 0) {
|
|
153
|
+
return this.notifications.warning('Please select at least one resource type to import.');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
modal.setOption('importProgress', `Importing ${selected.join(', ')}...`);
|
|
158
|
+
|
|
159
|
+
const response = await this.fetch.post(
|
|
160
|
+
'data/import',
|
|
161
|
+
{
|
|
162
|
+
resource_types: selected,
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
namespace: 'solid/int/v1',
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
if (response.success) {
|
|
170
|
+
this.notifications.success(`Successfully imported ${response.imported_count} resources!`);
|
|
171
|
+
this.hostRouter.refresh();
|
|
172
|
+
return modal.done();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
this.notifications.error(response.error || 'Failed to import resources.');
|
|
176
|
+
modal.setOption('importProgress', null);
|
|
177
|
+
} catch (error) {
|
|
178
|
+
modal.setOption('importProgress', null);
|
|
179
|
+
this.notifications.serverError(error);
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
@action deleteItem(item) {
|
|
186
|
+
this.modalsManager.confirm({
|
|
187
|
+
title: `Are you sure you want to delete this ${item.type}?`,
|
|
188
|
+
body: `Deleting "${item.name}" will permanently remove it from your storage. This is irreversible!`,
|
|
189
|
+
acceptButtonText: 'Delete Forever',
|
|
190
|
+
confirm: async () => {
|
|
191
|
+
try {
|
|
192
|
+
await this.fetch.delete(`data/${item.type}/${item.slug}`, {}, { namespace: 'solid/int/v1' });
|
|
193
|
+
this.notifications.success(`${item.type === 'folder' ? 'Folder' : 'File'} deleted successfully!`);
|
|
194
|
+
this.hostRouter.refresh();
|
|
195
|
+
} catch (error) {
|
|
196
|
+
this.notifications.serverError(error);
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@action deleteSelected() {
|
|
203
|
+
const selected = this.table.selectedRows;
|
|
204
|
+
|
|
205
|
+
this.crud.bulkDelete(selected, {
|
|
206
|
+
modelNamePath: 'name',
|
|
207
|
+
acceptButtonText: 'Delete All',
|
|
208
|
+
onSuccess: () => {
|
|
209
|
+
return this.hostRouter.refresh();
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
@task({ restartable: true }) *search(event) {
|
|
215
|
+
yield timeout(300);
|
|
216
|
+
const query = typeof event.target.value === 'string' ? event.target.value : '';
|
|
217
|
+
this.hostRouter.transitionTo('console.solid-protocol.data.index', { queryParams: { query } });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -1,10 +1,30 @@
|
|
|
1
1
|
import Controller from '@ember/controller';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
2
3
|
import { inject as service } from '@ember/service';
|
|
3
4
|
import { task } from 'ember-concurrency';
|
|
5
|
+
import { debug } from '@ember/debug';
|
|
4
6
|
|
|
5
7
|
export default class HomeController extends Controller {
|
|
6
8
|
@service fetch;
|
|
7
9
|
@service notifications;
|
|
10
|
+
@service hostRouter;
|
|
11
|
+
@service modalsManager;
|
|
12
|
+
@tracked authStatus = null;
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
super(...arguments);
|
|
16
|
+
this.checkAuthenticationStatus.perform();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@task *checkAuthenticationStatus() {
|
|
20
|
+
try {
|
|
21
|
+
const authStatus = yield this.fetch.get('authentication-status', {}, { namespace: 'solid/int/v1' });
|
|
22
|
+
this.authStatus = authStatus;
|
|
23
|
+
} catch (error) {
|
|
24
|
+
debug('Failed to check authentication status:' + error.message);
|
|
25
|
+
this.authStatus = { authenticated: false, error: error.message };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
8
28
|
|
|
9
29
|
@task *authenticate() {
|
|
10
30
|
try {
|
|
@@ -20,4 +40,68 @@ export default class HomeController extends Controller {
|
|
|
20
40
|
@task *getAccountIndex() {
|
|
21
41
|
yield this.fetch.get('account', {}, { namespace: 'solid/int/v1' });
|
|
22
42
|
}
|
|
43
|
+
|
|
44
|
+
@task *logout() {
|
|
45
|
+
try {
|
|
46
|
+
yield this.fetch.post('logout', {}, { namespace: 'solid/int/v1' });
|
|
47
|
+
this.notifications.success('Logged out successfully');
|
|
48
|
+
this.authStatus = { authenticated: false };
|
|
49
|
+
} catch (error) {
|
|
50
|
+
this.notifications.serverError(error);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@task *refreshStatus() {
|
|
55
|
+
yield this.checkAuthenticationStatus.perform();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@task *navigateToPods() {
|
|
59
|
+
yield this.hostRouter.transitionTo('console.solid-protocol.data');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@task *navigateToAccount() {
|
|
63
|
+
yield this.hostRouter.transitionTo('console.solid-protocol.account');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get isAuthenticated() {
|
|
67
|
+
return this.authStatus?.authenticated === true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
get userProfile() {
|
|
71
|
+
return this.authStatus?.profile?.parsed_profile || {};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
get webId() {
|
|
75
|
+
return this.authStatus?.profile?.webid;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
get userName() {
|
|
79
|
+
return this.userProfile.name || 'Unknown User';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
get userEmail() {
|
|
83
|
+
return this.userProfile.email || 'No email available';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
get serverUrl() {
|
|
87
|
+
// Extract server URL from webId or use default
|
|
88
|
+
const webId = this.webId;
|
|
89
|
+
if (webId) {
|
|
90
|
+
try {
|
|
91
|
+
const url = new URL(webId);
|
|
92
|
+
return `${url.protocol}//${url.host}`;
|
|
93
|
+
} catch (e) {
|
|
94
|
+
// Fallback
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return 'http://localhost:3000';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
get storageLocations() {
|
|
101
|
+
return this.userProfile.storage_locations || [];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
get hasStorageLocations() {
|
|
105
|
+
return this.storageLocations.length > 0;
|
|
106
|
+
}
|
|
23
107
|
}
|
package/addon/engine.js
CHANGED
|
@@ -2,13 +2,9 @@ import Engine from '@ember/engine';
|
|
|
2
2
|
import loadInitializers from 'ember-load-initializers';
|
|
3
3
|
import Resolver from 'ember-resolver';
|
|
4
4
|
import config from './config/environment';
|
|
5
|
-
import services from '@fleetbase/ember-core/exports
|
|
6
|
-
import AdminSolidServerConfigComponent from './components/admin/solid-server-config';
|
|
7
|
-
import SolidBrandIconComponent from './components/solid-brand-icon';
|
|
5
|
+
import { services, externalRoutes } from '@fleetbase/ember-core/exports';
|
|
8
6
|
|
|
9
7
|
const { modulePrefix } = config;
|
|
10
|
-
const externalRoutes = ['console', 'extensions'];
|
|
11
|
-
|
|
12
8
|
export default class SolidEngine extends Engine {
|
|
13
9
|
modulePrefix = modulePrefix;
|
|
14
10
|
Resolver = Resolver;
|
|
@@ -16,25 +12,6 @@ export default class SolidEngine extends Engine {
|
|
|
16
12
|
services,
|
|
17
13
|
externalRoutes,
|
|
18
14
|
};
|
|
19
|
-
setupExtension = function (app, engine, universe) {
|
|
20
|
-
// register menu item in header
|
|
21
|
-
universe.registerHeaderMenuItem('Solid', 'console.solid-protocol', { iconComponent: SolidBrandIconComponent, iconComponentOptions: { width: 19, height: 19 }, priority: 5 });
|
|
22
|
-
|
|
23
|
-
// register admin settings -- create a solid server menu panel with it's own setting options
|
|
24
|
-
universe.registerAdminMenuPanel(
|
|
25
|
-
'Solid Protocol',
|
|
26
|
-
[
|
|
27
|
-
{
|
|
28
|
-
title: 'Solid Server Config',
|
|
29
|
-
icon: 'sliders',
|
|
30
|
-
component: AdminSolidServerConfigComponent,
|
|
31
|
-
},
|
|
32
|
-
],
|
|
33
|
-
{
|
|
34
|
-
slug: 'solid-server',
|
|
35
|
-
}
|
|
36
|
-
);
|
|
37
|
-
};
|
|
38
15
|
}
|
|
39
16
|
|
|
40
17
|
loadInitializers(SolidEngine, modulePrefix);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { MenuItem, ExtensionComponent } from '@fleetbase/ember-core/contracts';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
setupExtension(app, universe) {
|
|
5
|
+
const menuService = universe.getService('menu');
|
|
6
|
+
|
|
7
|
+
// Register menu item in header
|
|
8
|
+
// const iconOptions = { iconComponent: new ExtensionComponent('@fleetbase/solid-engine', 'solid-brand-icon'), iconComponentOptions: { width: 19, height: 19 } };
|
|
9
|
+
menuService.registerHeaderMenuItem('Solid', 'console.solid-protocol', { priority: 5 });
|
|
10
|
+
|
|
11
|
+
// Register admin settings -- create a solid server menu panel with it's own setting options
|
|
12
|
+
universe.registerAdminMenuPanel(
|
|
13
|
+
'Solid Protocol',
|
|
14
|
+
[
|
|
15
|
+
new MenuItem({
|
|
16
|
+
title: 'Solid Server Config',
|
|
17
|
+
icon: 'sliders',
|
|
18
|
+
component: new ExtensionComponent('@fleetbase/solid-engine', 'admin/solid-server-config'),
|
|
19
|
+
}),
|
|
20
|
+
],
|
|
21
|
+
{
|
|
22
|
+
slug: 'solid-server',
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
},
|
|
26
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import Route from '@ember/routing/route';
|
|
2
|
+
import { inject as service } from '@ember/service';
|
|
3
|
+
|
|
4
|
+
export default class DataContentRoute extends Route {
|
|
5
|
+
@service fetch;
|
|
6
|
+
|
|
7
|
+
model({ slug }) {
|
|
8
|
+
// Fetch folder/container contents within the user's pod
|
|
9
|
+
return this.fetch.get('data/folder', { slug }, { namespace: 'solid/int/v1' });
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import Route from '@ember/routing/route';
|
|
2
|
+
import { inject as service } from '@ember/service';
|
|
3
|
+
|
|
4
|
+
export default class DataIndexRoute extends Route {
|
|
5
|
+
@service fetch;
|
|
6
|
+
|
|
7
|
+
queryParams = {
|
|
8
|
+
query: {
|
|
9
|
+
refreshModel: true,
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
model({ query }) {
|
|
14
|
+
// Fetch the user's primary pod data
|
|
15
|
+
return this.fetch.get('data', { query }, { namespace: 'solid/int/v1' });
|
|
16
|
+
}
|
|
17
|
+
}
|
package/addon/routes.js
CHANGED
|
@@ -3,12 +3,7 @@ import buildRoutes from 'ember-engines/routes';
|
|
|
3
3
|
export default buildRoutes(function () {
|
|
4
4
|
this.route('home', { path: '/' });
|
|
5
5
|
this.route('account');
|
|
6
|
-
this.route('
|
|
7
|
-
this.route('
|
|
8
|
-
this.route('content', { path: '/~/:slug' });
|
|
9
|
-
});
|
|
10
|
-
this.route('index', { path: '/' }, function () {
|
|
11
|
-
this.route('pod', { path: '/pod/:slug' });
|
|
12
|
-
});
|
|
6
|
+
this.route('data', function () {
|
|
7
|
+
this.route('content', { path: '/:slug' });
|
|
13
8
|
});
|
|
14
9
|
});
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
.solid-fleetbase-home-container {
|
|
2
|
-
margin: auto;
|
|
3
|
-
width: 1200px;
|
|
4
2
|
padding: 2rem;
|
|
5
3
|
}
|
|
6
4
|
|
|
@@ -18,6 +16,7 @@
|
|
|
18
16
|
|
|
19
17
|
body[data-theme='light'] .solid-fleetbase-home-container a:not([class*='text-']),
|
|
20
18
|
body[data-theme='dark'] .solid-fleetbase-home-container a:not([class*='text-']),
|
|
19
|
+
.solid-fleetbase-home-container a:not(.next-content-panel-header-left),
|
|
21
20
|
.solid-fleetbase-home-container a {
|
|
22
21
|
color: #60a5fa;
|
|
23
22
|
text-decoration: underline;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<Layout::Section::Body class="overflow-y-scroll h-full">
|
|
4
4
|
<div class="container mx-auto h-screen">
|
|
5
5
|
<div class="max-w-3xl my-10 mx-auto">
|
|
6
|
-
<ContentPanel @title="Solid Identity" @open={{true}} @
|
|
6
|
+
<ContentPanel @title="Solid Identity" @open={{true}} @wrapperClass="bordered-classic">
|
|
7
7
|
<form class="flex flex-col md:flex-row" {{on "submit" (perform this.saveProfile)}}>
|
|
8
8
|
<div class="w-32 flex flex-col justify-center mb-6 mr-6">
|
|
9
9
|
<Image src={{this.user.avatar_url}} @fallbackSrc={{config "defaultValues.userImage"}} alt={{this.user.name}} class="w-32 h-32 rounded-md" />
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
{{else}}
|
|
20
20
|
<FaIcon @icon="image" class="mr-1.5" />
|
|
21
21
|
<span>
|
|
22
|
-
{{t "
|
|
22
|
+
{{t "common.upload-new-photo"}}
|
|
23
23
|
</span>
|
|
24
24
|
{{/if}}
|
|
25
25
|
</a>
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
<InputGroup @name={{t "common.email"}} @type="email" @value={{this.user.email}} />
|
|
32
32
|
</div>
|
|
33
33
|
<div class="mt-3 flex items-center justify-end">
|
|
34
|
-
<Button @buttonType="submit" @type="primary" @size="lg" @icon="save" @text={{t "common.save-
|
|
34
|
+
<Button @buttonType="submit" @type="primary" @size="lg" @icon="save" @text={{t "common.save-changes"}} @onClick={{perform this.saveProfile}} @isLoading={{not this.saveProfile.isIdle}} />
|
|
35
35
|
</div>
|
|
36
36
|
</div>
|
|
37
37
|
</form>
|
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
<EmberWormhole @to="sidebar-menu-items">
|
|
2
2
|
<Layout::Sidebar::Item @route="console.solid-protocol.home" @icon="home">Home</Layout::Sidebar::Item>
|
|
3
|
-
<Layout::Sidebar::Item @route="console.solid-protocol.
|
|
4
|
-
|
|
5
|
-
<Layout::Sidebar::Item @route="console.solid-protocol.pods" @icon="folder-tree">All</Layout::Sidebar::Item>
|
|
6
|
-
{{#each this.pods as |pod|}}
|
|
7
|
-
<LinkTo @route="pods.explorer" @model={{pod}} @query={{hash id=pod.id pod=pod.id}} class="next-nav-item">
|
|
8
|
-
<div class="next-nav-item-icon-container">
|
|
9
|
-
<FaIcon @icon="folder" @size="xs" />
|
|
10
|
-
</div>
|
|
11
|
-
<div class="truncate w-10/12 ">{{pod.name}}</div>
|
|
12
|
-
</LinkTo>
|
|
13
|
-
{{/each}}
|
|
14
|
-
</Layout::Sidebar::Panel> --}}
|
|
3
|
+
<Layout::Sidebar::Item @route="console.solid-protocol.data" @icon="database">Data</Layout::Sidebar::Item>
|
|
4
|
+
|
|
15
5
|
<Layout::Sidebar::Item @route="console.solid-protocol.account" @icon="user">Account</Layout::Sidebar::Item>
|
|
16
6
|
</EmberWormhole>
|
|
17
7
|
|