@fleetbase/solid-engine 0.0.4 → 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.
Files changed (61) hide show
  1. package/ACL_SOLUTION.md +72 -0
  2. package/CSS_SCOPE_ISSUE.md +140 -0
  3. package/HOTFIX_SYNTAX_ERROR.md +100 -0
  4. package/MANUAL_ACL_SETUP.md +135 -0
  5. package/REFACTORING_SUMMARY.md +330 -0
  6. package/VERIFICATION_CHECKLIST.md +82 -0
  7. package/addon/components/modals/create-solid-folder.hbs +29 -0
  8. package/addon/components/modals/import-solid-resources.hbs +85 -0
  9. package/addon/controllers/data/content.js +17 -0
  10. package/addon/controllers/data/index.js +219 -0
  11. package/addon/controllers/home.js +84 -0
  12. package/addon/engine.js +1 -24
  13. package/addon/extension.js +26 -0
  14. package/addon/routes/data/content.js +11 -0
  15. package/addon/routes/data/index.js +17 -0
  16. package/addon/routes.js +2 -7
  17. package/addon/styles/solid-engine.css +1 -2
  18. package/addon/templates/account.hbs +3 -3
  19. package/addon/templates/application.hbs +2 -12
  20. package/addon/templates/data/content.hbs +48 -0
  21. package/addon/templates/{pods/explorer.hbs → data/index.hbs} +6 -5
  22. package/addon/templates/home.hbs +168 -10
  23. package/app/components/modals/{backup-pod.js → create-solid-folder.js} +1 -1
  24. package/app/components/modals/{resync-pod.js → import-solid-resources.js} +1 -1
  25. package/app/components/modals/{create-pod.js → setup-css-credentials.js} +1 -1
  26. package/composer.json +4 -10
  27. package/extension.json +1 -1
  28. package/index.js +0 -11
  29. package/package.json +8 -8
  30. package/server/migrations/2024_12_21_add_css_credentials_to_solid_identities_table.php +32 -0
  31. package/server/src/Client/OpenIDConnectClient.php +686 -15
  32. package/server/src/Client/SolidClient.php +104 -8
  33. package/server/src/Http/Controllers/DataController.php +261 -0
  34. package/server/src/Http/Controllers/OIDCController.php +42 -8
  35. package/server/src/Http/Controllers/SolidController.php +179 -85
  36. package/server/src/Models/SolidIdentity.php +13 -3
  37. package/server/src/Services/AclService.php +146 -0
  38. package/server/src/Services/PodService.php +863 -0
  39. package/server/src/Services/ResourceSyncService.php +336 -0
  40. package/server/src/Services/VehicleSyncService.php +289 -0
  41. package/server/src/Support/Utils.php +10 -0
  42. package/server/src/routes.php +25 -1
  43. package/addon/components/modals/backup-pod.hbs +0 -3
  44. package/addon/components/modals/create-pod.hbs +0 -5
  45. package/addon/components/modals/resync-pod.hbs +0 -3
  46. package/addon/controllers/pods/explorer/content.js +0 -12
  47. package/addon/controllers/pods/explorer.js +0 -149
  48. package/addon/controllers/pods/index/pod.js +0 -12
  49. package/addon/controllers/pods/index.js +0 -137
  50. package/addon/routes/pods/explorer/content.js +0 -10
  51. package/addon/routes/pods/explorer.js +0 -44
  52. package/addon/routes/pods/index/pod.js +0 -3
  53. package/addon/routes/pods/index.js +0 -21
  54. package/addon/templates/pods/explorer/content.hbs +0 -19
  55. package/addon/templates/pods/index/pod.hbs +0 -11
  56. package/addon/templates/pods/index.hbs +0 -19
  57. package/server/src/LegacyClient/Identity/IdentityProvider.php +0 -174
  58. package/server/src/LegacyClient/Identity/Profile.php +0 -18
  59. package/server/src/LegacyClient/OIDCClient.php +0 -350
  60. package/server/src/LegacyClient/Profile/WebID.php +0 -26
  61. package/server/src/LegacyClient/SolidClient.php +0 -271
@@ -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/services';
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('pods', function () {
7
- this.route('explorer', { path: '/explorer/:id' }, function () {
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}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
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 "console.account.index.upload-new"}}
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-button-text"}} @onClick={{perform this.saveProfile}} @isLoading={{not this.saveProfile.isIdle}} />
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.pods" @icon="folder-tree">Pods</Layout::Sidebar::Item>
4
- {{!-- <Layout::Sidebar::Panel @open={{true}} @title="Pods">
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
 
@@ -0,0 +1,48 @@
1
+ <Overlay @isOpen={{true}} @position="right" @noBackdrop={{true}} @fullHeight={{true}} @width="800px" @isResizable={{true}}>
2
+ <Overlay::Header @title={{@model.name}} @hideStatusDot={{true}} @titleWrapperClass="leading-5">
3
+ <div class="flex flex-1 justify-end">
4
+ <Button @type="default" @icon="times" @helpText="Close" @onClick={{this.back}} />
5
+ </div>
6
+ </Overlay::Header>
7
+
8
+ <Overlay::Body class="p-6">
9
+ <div class="space-y-4">
10
+ <div>
11
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">File Information</label>
12
+ <div class="bg-gray-50 dark:bg-gray-800 rounded-lg p-4 space-y-2">
13
+ <div class="flex justify-between">
14
+ <span class="text-gray-600 dark:text-gray-400">Name:</span>
15
+ <span class="text-gray-900 dark:text-white font-medium">{{@model.name}}</span>
16
+ </div>
17
+ <div class="flex justify-between">
18
+ <span class="text-gray-600 dark:text-gray-400">Type:</span>
19
+ <span class="text-gray-900 dark:text-white capitalize">{{@model.type}}</span>
20
+ </div>
21
+ <div class="flex justify-between">
22
+ <span class="text-gray-600 dark:text-gray-400">Size:</span>
23
+ <span class="text-gray-900 dark:text-white">{{@model.size}}</span>
24
+ </div>
25
+ <div class="flex justify-between">
26
+ <span class="text-gray-600 dark:text-gray-400">Created:</span>
27
+ <span class="text-gray-900 dark:text-white">{{@model.created_at}}</span>
28
+ </div>
29
+ </div>
30
+ </div>
31
+
32
+ {{#if @model.url}}
33
+ <div>
34
+ <Button @text="Open File" @icon="external-link-alt" @type="primary" @onClick={{this.viewFile}} class="w-full" />
35
+ </div>
36
+ {{/if}}
37
+
38
+ {{#if @model.content}}
39
+ <div>
40
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Preview</label>
41
+ <div class="bg-gray-50 dark:bg-gray-800 rounded-lg p-4 max-h-96 overflow-y-auto">
42
+ <pre class="text-sm text-gray-900 dark:text-white whitespace-pre-wrap">{{@model.content}}</pre>
43
+ </div>
44
+ </div>
45
+ {{/if}}
46
+ </div>
47
+ </Overlay::Body>
48
+ </Overlay>
@@ -1,4 +1,4 @@
1
- <ExplorerHeader @current={{@model.id}} @pod={{this.pod}} @onStateClicked={{this.viewContents}} @searchPlaceholder="Search Contents" @searchQuery={{this.query}} @onSearch={{perform this.search}}>
1
+ <Layout::Section::Header @title="Data" @searchPlaceholder="Search Files and Folders" @searchQuery={{this.query}} @onSearch={{perform this.search}}>
2
2
  <Button @icon="arrow-left" @onClick={{this.back}} @helpText="Go Back" class="mr-2" />
3
3
  <Button @icon="refresh" @onClick={{this.reload}} @helpText="Refresh" class="mr-2" />
4
4
  {{#if (safe-has this.table "selectedRows")}}
@@ -12,9 +12,10 @@
12
12
  </div>
13
13
  </DropdownButton>
14
14
  {{/if}}
15
- <Button @icon="plus" @iconPrefix="fas" @type="primary" @text="Create new Something" class="mr-2" @onClick={{this.createSomething}} />
16
- </ExplorerHeader>
15
+ <Button @icon="folder-plus" @iconPrefix="fas" @type="secondary" @text="New Folder" class="mr-2" @onClick={{this.createFolder}} />
16
+ <Button @icon="download" @iconPrefix="fas" @type="primary" @text="Import Resources" class="mr-2" @onClick={{this.importResources}} />
17
+ </Layout::Section::Header>
17
18
  <Layout::Section::Body>
18
- <Table @rows={{@model.contents}} @columns={{this.columns}} @selectable={{true}} @canSelectAll={{true}} @onSetup={{fn (mut this.table)}} @tfootVerticalOffset="53" @tfootVerticalOffsetElements=".next-view-section-subheader" />
19
+ <Table @rows={{@model.contents}} @columns={{this.columns}} @selectable={{true}} @canSelectAll={{true}} @onSetup={{fn (mut this.table)}} />
19
20
  </Layout::Section::Body>
20
- {{outlet}}
21
+ {{outlet}}