@fleetbase/solid-engine 0.0.2 → 0.0.3
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 +24 -0
- package/addon/components/explorer-header.hbs +18 -0
- package/addon/components/explorer-header.js +25 -0
- package/addon/components/modals/backup-pod.hbs +3 -0
- package/addon/components/modals/create-pod.hbs +5 -0
- package/addon/components/modals/resync-pod.hbs +3 -0
- package/addon/components/table/cell/pod-content-actions.hbs +32 -0
- package/addon/components/table/cell/pod-content-actions.js +73 -0
- package/addon/components/table/cell/pod-content-name.hbs +8 -0
- package/addon/components/table/cell/pod-content-name.js +16 -0
- package/addon/controllers/account.js +130 -0
- package/addon/controllers/application.js +16 -13
- package/addon/controllers/home.js +23 -0
- package/addon/controllers/pods/explorer/content.js +12 -0
- package/addon/controllers/pods/explorer.js +149 -0
- package/addon/controllers/pods/index/pod.js +12 -0
- package/addon/controllers/pods/index.js +137 -0
- package/addon/routes/account.js +3 -0
- package/addon/routes/home.js +3 -0
- package/addon/routes/pods/explorer/content.js +10 -0
- package/addon/routes/pods/explorer.js +44 -0
- package/addon/routes/pods/index/pod.js +3 -0
- package/addon/routes/pods/index.js +21 -0
- package/addon/routes.js +12 -1
- package/addon/services/explorer-state.js +101 -0
- package/addon/styles/solid-engine.css +46 -0
- package/addon/templates/account.hbs +42 -0
- package/addon/templates/application.hbs +17 -12
- package/addon/templates/home.hbs +11 -0
- package/addon/templates/pods/explorer/content.hbs +19 -0
- package/addon/templates/pods/explorer.hbs +20 -0
- package/addon/templates/pods/index/pod.hbs +11 -0
- package/addon/templates/pods/index.hbs +19 -0
- package/app/components/explorer-header.js +1 -0
- package/app/components/modals/backup-pod.js +1 -0
- package/app/components/modals/create-pod.js +1 -0
- package/app/components/modals/resync-pod.js +1 -0
- package/app/components/table/cell/pod-content-actions.js +1 -0
- package/app/components/table/cell/pod-content-name.js +1 -0
- package/app/controllers/account.js +1 -0
- package/app/controllers/home.js +1 -0
- package/app/controllers/pods/explorer/content.js +1 -0
- package/app/controllers/pods/explorer.js +1 -0
- package/app/controllers/pods/index/pod.js +1 -0
- package/app/controllers/pods/index.js +1 -0
- package/app/routes/account.js +1 -0
- package/app/routes/home.js +1 -0
- package/app/routes/pods/explorer/content.js +1 -0
- package/app/routes/pods/explorer.js +1 -0
- package/app/routes/pods/index/pod.js +1 -0
- package/app/routes/pods/index.js +1 -0
- package/app/services/explorer-state.js +1 -0
- package/app/templates/account.js +1 -0
- package/app/templates/home.js +1 -0
- package/app/templates/pods/explorer/content.js +1 -0
- package/app/templates/pods/explorer.js +1 -0
- package/app/templates/pods/index/pod.js +1 -0
- package/app/templates/pods/index.js +1 -0
- package/composer.json +9 -3
- package/extension.json +1 -1
- package/package.json +3 -3
- package/server/data/pods.json +328 -0
- package/server/src/Client/OpenIDConnectClient.php +2 -2
- package/server/src/Client/SolidClient.php +1 -1
- package/server/src/Http/Controllers/SolidController.php +57 -4
- package/server/src/LegacyClient/OIDCClient.php +1 -1
- package/server/src/LegacyClient/SolidClient.php +2 -1
- package/server/src/Support/Utils.php +38 -0
- package/server/src/routes.php +1 -0
package/README.md
CHANGED
|
@@ -61,6 +61,7 @@ Solid, an innovative technology developed by Sir Tim Berners-Lee, offers a groun
|
|
|
61
61
|
- Input their server
|
|
62
62
|
- Input their solid ID on the server
|
|
63
63
|
- Once installed, users signing up or added to the company within this instance can utilize Solid for data management.
|
|
64
|
+
- More info: The interface which allows the instance administrator to link the Fleetbase Solid extension to the Solid server of their preference. Due to the nature of how Solid is built on an identity basis, and Fleetbase is built as a multi-tenant platform. Each organization on Fleetbase when accessing the instance will be prompted to link their Solid OIDC account [https://github.com/fleetbase/solid/blob/main/addon/controllers/application.js#L16] to their Fleetbase organization per the instance. Once linked all data synced between Solid and Fleetbase will be via the SolidIdentity [https://github.com/fleetbase/solid/blob/main/server/src/Models/SolidIdentity.php]
|
|
64
65
|
|
|
65
66
|
### User Authentication and Account Creation:
|
|
66
67
|
|
|
@@ -69,8 +70,31 @@ Solid, an innovative technology developed by Sir Tim Berners-Lee, offers a groun
|
|
|
69
70
|
- Authorize fleetbase.io to access your Pod.
|
|
70
71
|
- Solid allows precise control over data access permissions. Note: The current UI version (node-solid-server V5.1) supports toggling global access permissions only. If you prefer granular control, uncheck all boxes and authorize. Then, manage permissions explicitly.
|
|
71
72
|
|
|
73
|
+
### Fleetbase UI Updates
|
|
74
|
+
|
|
75
|
+
- As per the latest release, this is the updated UI screenshots for Fleetbase for users to manage pods.
|
|
76
|
+
|
|
77
|
+
- You can see the full release details here: https://github.com/fleetbase/solid/pull/2
|
|
78
|
+
|
|
79
|
+
- Install Solid Extension and click link: 'Sign up for an account' This will take you to Solid to create your own Solid Server & Pods,
|
|
80
|
+
|
|
81
|
+

|
|
82
|
+
|
|
83
|
+
- Once you head to this link, you can create your own Solid Server. You should be able to generate as per the Screenshot:
|
|
84
|
+
|
|
85
|
+
<img width="1134" alt="image" src="https://github.com/fleetbase/solid/assets/58805033/97015745-a1a6-487a-a958-fe97d0a7bca7">
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
- Input your Solid Server details directly into Fleetbase in company admin settings
|
|
89
|
+
|
|
90
|
+

|
|
91
|
+
|
|
92
|
+
Next steps would be to continue to update the UI from feedback and also conduct thorough testing and documentation. UI enhancements will be things like viewing the specific pods created as well as last synced.
|
|
93
|
+
|
|
72
94
|
### Features:
|
|
73
95
|
|
|
96
|
+
Fleetbase has implemented a Solid Client which implements the Standard Solid authentication methods to communicate with the server. The Fleetbase SolidClient is able to communicate securely with the Solid protocol using the Standard DPoP encryption method for authentication provided by the Solid specification (https://solidproject.org/TR/oidc#tokens-id)
|
|
97
|
+
|
|
74
98
|
- Ability to link Fleetbase account with Solid Web ID later via user settings.
|
|
75
99
|
- View and manage data stored on Solid Pod:
|
|
76
100
|
- Orders
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<div id="next-view-section-subheader" class="next-view-section-subheader {{if @hideActions 'actions-hidden'}}" ...attributes>
|
|
2
|
+
<div id="next-view-section-subheader-left" class="next-view-section-subheader-left {{@leftSubheaderClass}}">
|
|
3
|
+
<div class="flex flex-row items-center">
|
|
4
|
+
<FaIcon @icon="folder-tree" @size="sm" class="{{@iconClass}} text-gray-200 dark:text-gray-600 mr-2" />
|
|
5
|
+
<div class="pod-explorer-breadcrumb-container">
|
|
6
|
+
{{#each this.state as |content|}}
|
|
7
|
+
<a href="#" class="pod-explorer-breadcrumb {{if (eq @current content.id) "active-breadcrumb"}} {{@breadcrumbClass}}" {{on "click" (fn this.onStateClicked content)}}>{{content.name}}</a>
|
|
8
|
+
{{/each}}
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
<div id="next-view-section-subheader-actions" class="next-view-section-subheader-actions {{@actionsWrapperClass}}">
|
|
13
|
+
{{#if @onSearch}}
|
|
14
|
+
<Input @type="text" @value={{@searchQuery}} aria-label={{t "common.search-input"}} placeholder={{or @searchPlaceholder (concat (t "common.search") " " (pluralize @title))}} class="w-64 mr-2 form-input form-input-sm {{@searchInputClass}}" {{on "keyup" @onSearch}} />
|
|
15
|
+
{{/if}}
|
|
16
|
+
{{yield}}
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { inject as service } from '@ember/service';
|
|
4
|
+
import { action } from '@ember/object';
|
|
5
|
+
|
|
6
|
+
export default class ExplorerHeaderComponent extends Component {
|
|
7
|
+
@service explorerState;
|
|
8
|
+
@tracked state = [];
|
|
9
|
+
|
|
10
|
+
constructor(owner, { pod }) {
|
|
11
|
+
super(...arguments);
|
|
12
|
+
this.state = this.explorerState.get(pod);
|
|
13
|
+
this.explorerState.on('change', (id, state) => {
|
|
14
|
+
if (id === pod) {
|
|
15
|
+
this.state = state;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@action onStateClicked(content) {
|
|
21
|
+
if (typeof this.args.onStateClicked === 'function') {
|
|
22
|
+
this.args.onStateClicked(content);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<Modal::Default @modalIsOpened={{@modalIsOpened}} @options={{@options}} @confirm={{@onConfirm}} @decline={{@onDecline}}>
|
|
2
|
+
<div class="modal-body-container">
|
|
3
|
+
<InputGroup @name="Pod Name" @value={{@options.pod.name}} @helpText="Input a name for your new Pod" />
|
|
4
|
+
</div>
|
|
5
|
+
</Modal::Default>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<div class="cell-dropdown-button overflow-visible {{@column.wrapperClass}}" {{did-insert this.setupComponent}} ...attributes>
|
|
2
|
+
<DropdownButton @icon={{@column.ddButtonIcon}} @iconPrefix={{@column.ddButtonIconPrefix}} @text={{this.buttonText}} @size="xs" @horizontalPosition="left" @calculatePosition={{this.calculatePosition}} @renderInPlace={{true}} as |dd|>
|
|
3
|
+
<div class="next-dd-menu mt-0i" role="menu" aria-orientation="vertical" aria-labelledby="user-menu">
|
|
4
|
+
{{#if @column.ddMenuLabel}}
|
|
5
|
+
<div class="px-1">
|
|
6
|
+
<div class="text-sm flex flex-row items-center px-3 py-1 rounded-md my-1 text-gray-800 dark:text-gray-300">
|
|
7
|
+
{{@column.ddMenuLabel}}
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="next-dd-menu-seperator"></div>
|
|
11
|
+
{{/if}}
|
|
12
|
+
{{#each this.actions as |action|}}
|
|
13
|
+
{{#if action.separator}}
|
|
14
|
+
<div class="next-dd-menu-seperator"></div>
|
|
15
|
+
{{else}}
|
|
16
|
+
{{#if (is-dd-item-visible @row action.isVisible)}}
|
|
17
|
+
<div role="group" class="px-1">
|
|
18
|
+
<a href="javascript:;" role="menuitem" class="next-dd-item {{action.class}}" {{on "click" (fn this.onDropdownItemClick action @row dd)}}>
|
|
19
|
+
{{#if action.icon}}
|
|
20
|
+
<span class="mr-1">
|
|
21
|
+
<FaIcon class={{action.iconClass}} @icon={{action.icon}} @prefix={{action.iconPrefix}} />
|
|
22
|
+
</span>
|
|
23
|
+
{{/if}}
|
|
24
|
+
{{action.label}}
|
|
25
|
+
</a>
|
|
26
|
+
</div>
|
|
27
|
+
{{/if}}
|
|
28
|
+
{{/if}}
|
|
29
|
+
{{/each}}
|
|
30
|
+
</div>
|
|
31
|
+
</DropdownButton>
|
|
32
|
+
</div>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { action, computed } from '@ember/object';
|
|
4
|
+
import { isArray } from '@ember/array';
|
|
5
|
+
|
|
6
|
+
export default class TableCellPodContentActionsComponent extends Component {
|
|
7
|
+
constructor(owner, { column, row }) {
|
|
8
|
+
super(...arguments);
|
|
9
|
+
if (isArray(column.actions)) {
|
|
10
|
+
this.actions = column.actions;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (typeof column.actions === 'function') {
|
|
14
|
+
this.actions = column.actions(row);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@tracked actions = [];
|
|
19
|
+
@tracked defaultButtonText = 'Actions';
|
|
20
|
+
|
|
21
|
+
@computed('args.column.ddButtonText', 'defaultButtonText') get buttonText() {
|
|
22
|
+
const { ddButtonText } = this.args.column;
|
|
23
|
+
|
|
24
|
+
if (ddButtonText === undefined) {
|
|
25
|
+
return this.defaultButtonText;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (ddButtonText === false) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return ddButtonText;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@action setupComponent(dropdownWrapperNode) {
|
|
36
|
+
const tableCellNode = this.getOwnerTableCell(dropdownWrapperNode);
|
|
37
|
+
tableCellNode.style.overflow = 'visible';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@action getOwnerTableCell(dropdownWrapperNode) {
|
|
41
|
+
while (dropdownWrapperNode) {
|
|
42
|
+
dropdownWrapperNode = dropdownWrapperNode.parentNode;
|
|
43
|
+
|
|
44
|
+
if (dropdownWrapperNode.tagName.toLowerCase() === 'td') {
|
|
45
|
+
return dropdownWrapperNode;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@action onDropdownItemClick(columnAction, row, dd) {
|
|
53
|
+
if (typeof dd?.actions?.close === 'function') {
|
|
54
|
+
dd.actions.close();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (typeof columnAction?.fn === 'function') {
|
|
58
|
+
columnAction.fn(row);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@action calculatePosition(trigger) {
|
|
63
|
+
let { width } = trigger.getBoundingClientRect();
|
|
64
|
+
|
|
65
|
+
let style = {
|
|
66
|
+
marginTop: '0px',
|
|
67
|
+
right: width + 3,
|
|
68
|
+
top: 0,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return { style };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<a href="#" {{on "click" this.onClick}} class={{@column.anchorClass}} disabled={{not @value}} ...attributes>
|
|
2
|
+
{{#if (has-block)}}
|
|
3
|
+
{{yield}}
|
|
4
|
+
{{else}}
|
|
5
|
+
<FaIcon @icon={{@row.type}} @size="xs" class="mr-1" />
|
|
6
|
+
<span class={{@column.anchorSpanClass}}>{{or @value @column.anchorText "-"}}</span>
|
|
7
|
+
{{/if}}
|
|
8
|
+
</a>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { action } from '@ember/object';
|
|
3
|
+
|
|
4
|
+
export default class TableCellPodContentNameComponent extends Component {
|
|
5
|
+
@action onClick() {
|
|
6
|
+
const { column, row } = this.args;
|
|
7
|
+
|
|
8
|
+
if (typeof column?.action === 'function') {
|
|
9
|
+
column.action(row);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (typeof column?.onClick === 'function') {
|
|
13
|
+
column.onClick(row, ...arguments);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import Controller from '@ember/controller';
|
|
2
|
+
import { inject as service } from '@ember/service';
|
|
3
|
+
import { action } from '@ember/object';
|
|
4
|
+
import { alias } from '@ember/object/computed';
|
|
5
|
+
import { task } from 'ember-concurrency';
|
|
6
|
+
|
|
7
|
+
export default class AccountController extends Controller {
|
|
8
|
+
/**
|
|
9
|
+
* Inject the `currentUser` service.
|
|
10
|
+
*
|
|
11
|
+
* @memberof ConsoleAccountIndexController
|
|
12
|
+
*/
|
|
13
|
+
@service currentUser;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Inject the `fetch` service.
|
|
17
|
+
*
|
|
18
|
+
* @memberof ConsoleAccountIndexController
|
|
19
|
+
*/
|
|
20
|
+
@service fetch;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Inject the `notifications` service.
|
|
24
|
+
*
|
|
25
|
+
* @memberof ConsoleAccountIndexController
|
|
26
|
+
*/
|
|
27
|
+
@service notifications;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Inject the `modalsManager` service.
|
|
31
|
+
*
|
|
32
|
+
* @memberof ConsoleAccountIndexController
|
|
33
|
+
*/
|
|
34
|
+
@service modalsManager;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Alias to the currentUser service user record.
|
|
38
|
+
*
|
|
39
|
+
* @memberof ConsoleAccountIndexController
|
|
40
|
+
*/
|
|
41
|
+
@alias('currentUser.user') user;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Handle upload of new photo
|
|
45
|
+
*
|
|
46
|
+
* @param {UploadFile} file
|
|
47
|
+
* @memberof ConsoleAccountIndexController
|
|
48
|
+
*/
|
|
49
|
+
@action uploadNewPhoto(file) {
|
|
50
|
+
return this.fetch.uploadFile.perform(
|
|
51
|
+
file,
|
|
52
|
+
{
|
|
53
|
+
path: `uploads/${this.user.company_uuid}/users/${this.user.slug}`,
|
|
54
|
+
subject_uuid: this.user.id,
|
|
55
|
+
subject_type: 'user',
|
|
56
|
+
type: 'user_avatar',
|
|
57
|
+
},
|
|
58
|
+
(uploadedFile) => {
|
|
59
|
+
this.user.setProperties({
|
|
60
|
+
avatar_uuid: uploadedFile.id,
|
|
61
|
+
avatar_url: uploadedFile.url,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return this.user.save();
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Starts the task to change password
|
|
71
|
+
*
|
|
72
|
+
* @param {Event} event
|
|
73
|
+
* @memberof ConsoleAccountIndexController
|
|
74
|
+
*/
|
|
75
|
+
@task *saveProfile(event) {
|
|
76
|
+
// If from event fired
|
|
77
|
+
if (event instanceof Event) {
|
|
78
|
+
event.preventDefault();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let canUpdateProfile = true;
|
|
82
|
+
// If email has been changed prompt for password validation
|
|
83
|
+
if (this.changedUserAttribute('email')) {
|
|
84
|
+
canUpdateProfile = yield this.validatePassword.perform();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (canUpdateProfile === true) {
|
|
88
|
+
try {
|
|
89
|
+
const user = yield this.user.save();
|
|
90
|
+
this.notifications.success('Profile changes saved.');
|
|
91
|
+
this.currentUser.set('user', user);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
this.notifications.serverError(error);
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
this.user.rollbackAttributes();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Task to validate current password
|
|
102
|
+
*
|
|
103
|
+
* @return {boolean}
|
|
104
|
+
* @memberof ConsoleAccountIndexController
|
|
105
|
+
*/
|
|
106
|
+
@task *validatePassword() {
|
|
107
|
+
let isPasswordValid = false;
|
|
108
|
+
|
|
109
|
+
yield this.modalsManager.show('modals/validate-password', {
|
|
110
|
+
body: 'You must validate your password to update the account email address.',
|
|
111
|
+
onValidated: (isValid) => {
|
|
112
|
+
isPasswordValid = isValid;
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return isPasswordValid;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Checks if any user attribute has been changed
|
|
121
|
+
*
|
|
122
|
+
* @param {string} attributeKey
|
|
123
|
+
* @return {boolean}
|
|
124
|
+
* @memberof ConsoleAccountIndexController
|
|
125
|
+
*/
|
|
126
|
+
changedUserAttribute(attributeKey) {
|
|
127
|
+
const changedAttributes = this.user.changedAttributes();
|
|
128
|
+
return changedAttributes[attributeKey] !== undefined;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -1,27 +1,30 @@
|
|
|
1
1
|
import Controller from '@ember/controller';
|
|
2
2
|
import { inject as service } from '@ember/service';
|
|
3
|
-
import {
|
|
3
|
+
import { tracked } from '@glimmer/tracking';
|
|
4
|
+
import { task, timeout } from 'ember-concurrency';
|
|
4
5
|
|
|
5
6
|
export default class ApplicationController extends Controller {
|
|
6
|
-
@service universe;
|
|
7
7
|
@service fetch;
|
|
8
|
+
@service appCache;
|
|
9
|
+
@tracked pods = [];
|
|
8
10
|
|
|
9
11
|
constructor() {
|
|
10
12
|
super(...arguments);
|
|
11
|
-
this.
|
|
12
|
-
sidebarContext.hideNow();
|
|
13
|
-
});
|
|
13
|
+
this.getPods.perform();
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
@task *
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
@task *getPods() {
|
|
17
|
+
yield timeout(600);
|
|
18
|
+
|
|
19
|
+
if (this.appCache.has('solid:pods')) {
|
|
20
|
+
this.pods = this.appCache.get('solid:pods', []);
|
|
21
|
+
return;
|
|
20
22
|
}
|
|
21
|
-
}
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
try {
|
|
25
|
+
this.pods = yield this.fetch.get('pods', {}, { namespace: 'solid/int/v1' });
|
|
26
|
+
} catch (error) {
|
|
27
|
+
// silence
|
|
28
|
+
}
|
|
26
29
|
}
|
|
27
30
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import Controller from '@ember/controller';
|
|
2
|
+
import { inject as service } from '@ember/service';
|
|
3
|
+
import { task } from 'ember-concurrency';
|
|
4
|
+
|
|
5
|
+
export default class HomeController extends Controller {
|
|
6
|
+
@service fetch;
|
|
7
|
+
@service notifications;
|
|
8
|
+
|
|
9
|
+
@task *authenticate() {
|
|
10
|
+
try {
|
|
11
|
+
const { authenticationUrl, identifier } = yield this.fetch.get('request-authentication', {}, { namespace: 'solid/int/v1' });
|
|
12
|
+
if (authenticationUrl) {
|
|
13
|
+
window.location.href = `${authenticationUrl}/${identifier}`;
|
|
14
|
+
}
|
|
15
|
+
} catch (error) {
|
|
16
|
+
this.notifications.serverError(error);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@task *getAccountIndex() {
|
|
21
|
+
yield this.fetch.get('account', {}, { namespace: 'solid/int/v1' });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import Controller from '@ember/controller';
|
|
2
|
+
import { action } from '@ember/object';
|
|
3
|
+
|
|
4
|
+
export default class PodsExplorerContentController extends Controller {
|
|
5
|
+
@action setOverlayContext(overlayContextApi) {
|
|
6
|
+
this.overlayContextApi = overlayContextApi;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
@action onPressClose() {
|
|
10
|
+
window.history.back();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
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 PodsExplorerController extends Controller {
|
|
8
|
+
@service hostRouter;
|
|
9
|
+
@service fetch;
|
|
10
|
+
@service notifications;
|
|
11
|
+
@service explorerState;
|
|
12
|
+
@service modalsManager;
|
|
13
|
+
@service crud;
|
|
14
|
+
@tracked cursor = '';
|
|
15
|
+
@tracked pod = '';
|
|
16
|
+
@tracked query = '';
|
|
17
|
+
queryParams = ['cursor', 'pod', 'query'];
|
|
18
|
+
columns = [
|
|
19
|
+
{
|
|
20
|
+
label: 'Name',
|
|
21
|
+
valuePath: 'name',
|
|
22
|
+
width: '75%',
|
|
23
|
+
cellComponent: 'table/cell/pod-content-name',
|
|
24
|
+
onClick: this.viewContents,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
label: 'Type',
|
|
28
|
+
valuePath: 'type',
|
|
29
|
+
cellClassNames: 'capitalize',
|
|
30
|
+
width: '5%',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
label: 'Size',
|
|
34
|
+
valuePath: 'size',
|
|
35
|
+
width: '5%',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
label: 'Created At',
|
|
39
|
+
valuePath: 'created_at',
|
|
40
|
+
width: '15%',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
label: '',
|
|
44
|
+
cellComponent: 'table/cell/pod-content-actions',
|
|
45
|
+
ddButtonText: false,
|
|
46
|
+
ddButtonIcon: 'ellipsis-h',
|
|
47
|
+
ddButtonIconPrefix: 'fas',
|
|
48
|
+
ddMenuLabel: 'Actions',
|
|
49
|
+
cellClassNames: 'overflow-visible',
|
|
50
|
+
wrapperClass: 'flex items-center justify-end mx-2',
|
|
51
|
+
width: '10%',
|
|
52
|
+
actions: (content) => {
|
|
53
|
+
return [
|
|
54
|
+
{
|
|
55
|
+
label: content.type === 'folder' ? 'Browse Folder' : 'View Contents',
|
|
56
|
+
fn: this.viewContents,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
separator: true,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
label: 'Delete',
|
|
63
|
+
fn: this.deleteSomething,
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
},
|
|
67
|
+
sortable: false,
|
|
68
|
+
filterable: false,
|
|
69
|
+
resizable: false,
|
|
70
|
+
searchable: false,
|
|
71
|
+
},
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
@action reload() {
|
|
75
|
+
this.hostRouter.refresh();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@action back() {
|
|
79
|
+
if (typeof this.cursor === 'string' && this.cursor.length && this.cursor !== this.model.id) {
|
|
80
|
+
const current = this.reverseCursor();
|
|
81
|
+
return this.hostRouter.transitionTo('console.solid-protocol.pods.explorer', current, { queryParams: { cursor: this.cursor, pod: this.pod } });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this.hostRouter.transitionTo('console.solid-protocol.pods.index');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@action viewContents(content) {
|
|
88
|
+
if (content.type === 'folder') {
|
|
89
|
+
return this.hostRouter.transitionTo('console.solid-protocol.pods.explorer', content, { queryParams: { cursor: this.trackCursor(content), pod: this.pod } });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (content.type === 'file') {
|
|
93
|
+
return this.hostRouter.transitionTo('console.solid-protocol.pods.explorer.content', content);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return this.hostRouter.transitionTo('console.solid-protocol.pods.explorer', this.pod, { queryParams: { cursor: this.trackCursor(content), pod: this.pod } });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@action deleteSomething() {
|
|
100
|
+
this.modalsManager.confirm({
|
|
101
|
+
title: 'Are you sure you want to delete this content?',
|
|
102
|
+
body: 'Deleting this Content will remove this content from this pod. This is irreversible!',
|
|
103
|
+
acceptButtonText: 'Delete Forever',
|
|
104
|
+
confirm: () => {},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@action deleteSelected() {
|
|
109
|
+
const selected = this.table.selectedRows;
|
|
110
|
+
|
|
111
|
+
this.crud.bulkDelete(selected, {
|
|
112
|
+
modelNamePath: 'name',
|
|
113
|
+
acceptButtonText: 'Delete All',
|
|
114
|
+
onSuccess: () => {
|
|
115
|
+
return this.hostRouter.refresh();
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
trackCursor(content) {
|
|
121
|
+
if (typeof this.cursor === 'string' && this.cursor.includes(content.id)) {
|
|
122
|
+
const segments = this.cursor.split(':');
|
|
123
|
+
const currentIndex = segments.findIndex((segment) => segment === content.id);
|
|
124
|
+
|
|
125
|
+
if (currentIndex > -1) {
|
|
126
|
+
const retainedSegments = segments.slice(0, currentIndex + 1);
|
|
127
|
+
this.cursor = retainedSegments.join(':');
|
|
128
|
+
return this.cursor;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
this.cursor = this.cursor ? `${this.cursor}:${content.id}` : content.id;
|
|
133
|
+
return this.cursor;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
reverseCursor() {
|
|
137
|
+
const segments = this.cursor.split(':');
|
|
138
|
+
segments.pop();
|
|
139
|
+
const current = segments[segments.length - 1];
|
|
140
|
+
this.cursor = segments.join(':');
|
|
141
|
+
return current;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
@task({ restartable: true }) *search(event) {
|
|
145
|
+
yield timeout(300);
|
|
146
|
+
const query = typeof event.target.value === 'string' ? event.target.value : '';
|
|
147
|
+
this.hostRouter.transitionTo('console.solid-protocol.pods.explorer', this.model.id, { queryParams: { cursor: this.cursor, query } });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import Controller from '@ember/controller';
|
|
2
|
+
import { action } from '@ember/object';
|
|
3
|
+
|
|
4
|
+
export default class PodsIndexPodController extends Controller {
|
|
5
|
+
@action setOverlayContext(overlayContextApi) {
|
|
6
|
+
this.overlayContextApi = overlayContextApi;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
@action onPressClose() {
|
|
10
|
+
window.history.back();
|
|
11
|
+
}
|
|
12
|
+
}
|