-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
UI: Add allocation directory rendering #5873
Changes from all commits
6f984ac
274d1d6
2796b07
5f0e0a9
875e09c
1d0c200
6025ede
ca2baf3
b8a5c42
75edb48
30e6771
3726a6d
71c8c82
ad5621b
976cff0
8eefd43
f1a4729
65635bc
0856bb0
df5b49e
abf4f94
acfed97
3eadd6d
4dae829
2a41ca6
095ac6a
cc36fe5
d89d5b7
92266ee
04975f1
999f57a
02867c0
7a15b23
c59282e
e77f2eb
53213a0
912f730
6ca3b5a
806b9bf
dfba2b2
c0fc9a4
9490c74
fc7baa7
3d0f4e2
d12df39
5593ae6
0e8f569
343a093
16fcf9c
8c28f57
3fafe68
df53346
8fd85b5
c0d1cdd
65ddf93
dab54a6
84a4b78
4e91134
f9a25d9
96200ba
d34433b
8d390f9
bdf681d
19313e6
3313a2e
ac94741
74fc0ce
0ca2353
d7232fa
23623ec
6aae341
d0f6028
853add7
492e1d7
cc93a79
4c13f15
da971d6
aa2c08d
4e8e2f3
6d53d05
c601a27
d6f71f2
7c0b35c
48ce41f
07e8863
2215038
4c845e8
ea6685d
94de177
95664ee
dbba6a2
7793c85
51079cb
506d235
a62308d
f6421b4
e4da8bf
09d88f3
ae93227
437492b
2d221bd
d18ede9
24c25f6
ea4fd68
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import ApplicationAdapter from './application'; | ||
import { inject as service } from '@ember/service'; | ||
|
||
export default ApplicationAdapter.extend({ | ||
token: service(), | ||
|
||
ls(model, path) { | ||
return this.token | ||
.authorizedRequest(`/v1/client/fs/ls/${model.allocation.id}?path=${path}`) | ||
.then(handleFSResponse); | ||
}, | ||
|
||
stat(model, path) { | ||
return this.token | ||
.authorizedRequest(`/v1/client/fs/stat/${model.allocation.id}?path=${path}`) | ||
.then(handleFSResponse); | ||
}, | ||
}); | ||
|
||
async function handleFSResponse(response) { | ||
if (response.ok) { | ||
return response.json(); | ||
} else { | ||
const body = await response.text(); | ||
|
||
// TODO update this if/when endpoint returns 404 as expected | ||
const statusIs500 = response.status === 500; | ||
const bodyIncludes404Text = body.includes('no such file or directory'); | ||
|
||
const translatedCode = statusIs500 && bodyIncludes404Text ? 404 : response.status; | ||
|
||
throw { | ||
code: translatedCode, | ||
toString: () => body, | ||
}; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import Component from '@ember/component'; | ||
import { computed } from '@ember/object'; | ||
import { isEmpty } from '@ember/utils'; | ||
|
||
export default Component.extend({ | ||
tagName: '', | ||
|
||
pathToEntry: computed('path', 'entry.Name', function() { | ||
const pathWithNoLeadingSlash = this.get('path').replace(/^\//, ''); | ||
const name = this.get('entry.Name'); | ||
|
||
if (isEmpty(pathWithNoLeadingSlash)) { | ||
return name; | ||
} else { | ||
return `${pathWithNoLeadingSlash}/${name}`; | ||
} | ||
}), | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import Component from '@ember/component'; | ||
import { inject as service } from '@ember/service'; | ||
import { equal, or } from '@ember/object/computed'; | ||
|
||
export default Component.extend({ | ||
router: service(), | ||
|
||
tagName: '', | ||
|
||
fsIsActive: equal('router.currentRouteName', 'allocations.allocation.task.fs'), | ||
fsRootIsActive: equal('router.currentRouteName', 'allocations.allocation.task.fs-root'), | ||
|
||
filesLinkActive: or('fsIsActive', 'fsRootIsActive'), | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import FSController from './fs'; | ||
|
||
export default FSController.extend(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import Controller from '@ember/controller'; | ||
import { computed } from '@ember/object'; | ||
import { filterBy } from '@ember/object/computed'; | ||
import { isEmpty } from '@ember/utils'; | ||
|
||
export default Controller.extend({ | ||
path: null, | ||
task: null, | ||
directoryEntries: null, | ||
isFile: null, | ||
|
||
directories: filterBy('directoryEntries', 'IsDir'), | ||
files: filterBy('directoryEntries', 'IsDir', false), | ||
backspace marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
breadcrumbs: computed('path', function() { | ||
const breadcrumbs = this.path | ||
.split('/') | ||
.reject(isEmpty) | ||
.reduce((breadcrumbs, pathSegment, index) => { | ||
let breadcrumbPath; | ||
|
||
if (index > 0) { | ||
const lastBreadcrumb = breadcrumbs[index - 1]; | ||
breadcrumbPath = `${lastBreadcrumb.path}/${pathSegment}`; | ||
} else { | ||
breadcrumbPath = pathSegment; | ||
} | ||
|
||
breadcrumbs.push({ | ||
name: pathSegment, | ||
path: breadcrumbPath, | ||
}); | ||
|
||
return breadcrumbs; | ||
}, []); | ||
|
||
if (breadcrumbs.length) { | ||
breadcrumbs[breadcrumbs.length - 1].isLast = true; | ||
} | ||
|
||
return breadcrumbs; | ||
}), | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import FSRoute from './fs'; | ||
|
||
export default FSRoute.extend({ | ||
templateName: 'allocations/allocation/task/fs', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It personally trips me up when there is a route that has no template due to something like this, but I'm willing to try it out. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, I don’t love it either 😐 |
||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import Route from '@ember/routing/route'; | ||
import RSVP from 'rsvp'; | ||
import notifyError from 'nomad-ui/utils/notify-error'; | ||
|
||
export default Route.extend({ | ||
model({ path = '/' }) { | ||
const decodedPath = decodeURIComponent(path); | ||
const task = this.modelFor('allocations.allocation.task'); | ||
|
||
const pathWithTaskName = `${task.name}${decodedPath.startsWith('/') ? '' : '/'}${decodedPath}`; | ||
|
||
return task | ||
.stat(pathWithTaskName) | ||
.then(statJson => { | ||
if (statJson.IsDir) { | ||
return RSVP.hash({ | ||
path: decodedPath, | ||
task, | ||
directoryEntries: task.ls(pathWithTaskName).catch(notifyError(this)), | ||
isFile: false, | ||
}); | ||
} else { | ||
return { | ||
path: decodedPath, | ||
task, | ||
isFile: true, | ||
}; | ||
} | ||
}) | ||
.catch(notifyError(this)); | ||
}, | ||
|
||
setupController(controller, { path, task, directoryEntries, isFile } = {}) { | ||
this._super(...arguments); | ||
controller.setProperties({ path, task, directoryEntries, isFile }); | ||
}, | ||
}); |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
.fs-explorer { | ||
width: 100%; | ||
|
||
.table.boxed-section-body.is-full-bleed { | ||
border: 1px solid $grey-blue; | ||
} | ||
|
||
tbody { | ||
a { | ||
text-decoration: none; | ||
color: inherit; | ||
|
||
&:hover { | ||
.name { | ||
text-decoration: underline; | ||
} | ||
} | ||
} | ||
} | ||
|
||
a { | ||
position: relative; | ||
|
||
// This is adapted from Bulma’s .button.is-loading::after | ||
&.ember-transitioning-in::after { | ||
animation: spinAround 500ms infinite linear; | ||
border: 2px solid $grey-light; | ||
border-radius: 290486px; | ||
border-right-color: transparent; | ||
border-top-color: transparent; | ||
content: ''; | ||
display: block; | ||
height: 1em; | ||
width: 1em; | ||
position: absolute; | ||
right: -1.5em; | ||
top: calc(50% - (1em / 2)); | ||
} | ||
} | ||
|
||
.breadcrumb { | ||
margin: 0; | ||
|
||
li::before { | ||
color: $grey-light; | ||
} | ||
|
||
a { | ||
padding-top: 0; | ||
padding-bottom: 0; | ||
color: $blue; | ||
opacity: 1; | ||
font-weight: $weight-bold; | ||
text-decoration: none; | ||
|
||
&:hover { | ||
text-decoration: underline; | ||
} | ||
|
||
&.ember-transitioning-in { | ||
margin-right: 1.5em; | ||
|
||
&::after { | ||
right: -1em; | ||
} | ||
} | ||
} | ||
|
||
.is-active a { | ||
color: $black; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,65 @@ | ||
{{outlet}} | ||
{{task-subnav task=task}} | ||
<section class="section is-closer"> | ||
{{#if task.isRunning}} | ||
<div class="fs-explorer boxed-section"> | ||
<div class="boxed-section-head"> | ||
<nav class="breadcrumb" data-test-fs-breadcrumbs> | ||
<ul> | ||
<li class={{if breadcrumbs "" "is-active"}}> | ||
{{#link-to "allocations.allocation.task.fs-root" task.allocation task activeClass="is-active"}} | ||
{{task.name}} | ||
{{/link-to}} | ||
</li> | ||
{{#each breadcrumbs as |breadcrumb|}} | ||
<li class={{if breadcrumb.isLast "is-active"}}> | ||
{{#link-to "allocations.allocation.task.fs" task.allocation task breadcrumb.path activeClass="is-active"}} | ||
{{breadcrumb.name}} | ||
{{/link-to}} | ||
</li> | ||
{{/each}} | ||
</ul> | ||
</nav> | ||
</div> | ||
|
||
{{#if isFile}} | ||
<div class="boxed-section-body"> | ||
<div data-test-file-viewer>placeholder file viewer</div> | ||
</div> | ||
{{else}} | ||
{{! template-lint-disable table-groups }} | ||
<table class="table boxed-section-body is-full-bleed is-compact"> | ||
{{#if directoryEntries}} | ||
<thead> | ||
<tr> | ||
<th class="is-two-thirds">Name</th> | ||
<th class="has-text-right">File Size</th> | ||
<th class="has-text-right">Last Modified</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{{#each (append directories files) as |entry|}} | ||
{{fs-directory-entry path=path task=task entry=entry}} | ||
{{/each}} | ||
</tbody> | ||
{{else}} | ||
<tbody> | ||
<tr data-test-entry> | ||
<td colspan="3"> | ||
<span data-test-empty-icon>{{x-icon "alert-circle-outline"}}</span> | ||
<span class="name" data-test-name>Directory is empty</span> | ||
</td> | ||
</tr> | ||
</tbody> | ||
{{/if}} | ||
</table> | ||
{{/if}} | ||
</div> | ||
{{else}} | ||
<div data-test-not-running class="empty-message"> | ||
<h3 data-test-not-running-headline class="empty-message-headline">Task is not Running</h3> | ||
<p data-test-not-running-body class="empty-message-body"> | ||
Cannot access files of a task that is not running. | ||
</p> | ||
</div> | ||
{{/if}} | ||
</section> |
This file was deleted.
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’m awaiting feedback on whether 500-as-404 works similarly across platforms, so this may not be quite ready, but everything else is apart from the custom Mirage scenario that I can remove later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed this to a
TODO
as I think it’s fine to leave in for now and return to if the API changes.