mirror of https://github.com/apache/cloudstack.git
config: add actions for compute, storage and network, custom action stubs
Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
parent
598476535a
commit
7b78f4d427
|
|
@ -31,7 +31,6 @@ export default {
|
|||
name: 'settings',
|
||||
component: () => import('@/views/setting/ResourceSettingsTab.vue')
|
||||
}],
|
||||
hidden: ['instancename', 'account'],
|
||||
actions: [
|
||||
{
|
||||
api: 'deployVirtualMachine',
|
||||
|
|
@ -53,7 +52,7 @@ export default {
|
|||
label: 'Start VM',
|
||||
dataView: true,
|
||||
groupAction: true,
|
||||
hidden: (record) => { return record.state !== 'Stopped' },
|
||||
show: (record) => { return ['Stopped'].includes(record.state) },
|
||||
args: ['podid', 'clusterid', 'hostid']
|
||||
},
|
||||
{
|
||||
|
|
@ -62,82 +61,97 @@ export default {
|
|||
label: 'label.action.stop.instance',
|
||||
dataView: true,
|
||||
groupAction: true,
|
||||
options: ['podid', 'clusterid', 'hostid'],
|
||||
hidden: (record) => { return record.state !== 'Running' }
|
||||
args: ['podid', 'clusterid', 'hostid'],
|
||||
show: (record) => { return ['Running'].includes(record.state) }
|
||||
},
|
||||
{
|
||||
api: 'rebootVirtualMachine',
|
||||
icon: 'sync',
|
||||
icon: 'reload',
|
||||
label: 'label.action.reboot.instance',
|
||||
dataView: true,
|
||||
hidden: (record) => { return record.state !== 'Running' }
|
||||
show: (record) => { return ['Running'].includes(record.state) }
|
||||
},
|
||||
{
|
||||
api: 'restoreVirtualMachine',
|
||||
icon: 'usb',
|
||||
icon: 'sync',
|
||||
label: 'label.reinstall.vm',
|
||||
dataView: true,
|
||||
args: ['virtualmachineid']
|
||||
},
|
||||
{
|
||||
api: 'updateVMAffinityGroup',
|
||||
icon: 'swap',
|
||||
label: 'label.change.affinity',
|
||||
dataView: true,
|
||||
args: ['id', 'serviceofferingid']
|
||||
},
|
||||
{
|
||||
api: 'changeServiceForVirtualMachine',
|
||||
icon: 'sliders',
|
||||
label: 'Change Service Offering',
|
||||
dataView: true,
|
||||
args: ['id', 'serviceofferingid']
|
||||
},
|
||||
{
|
||||
api: 'createVMSnapshot',
|
||||
icon: 'camera',
|
||||
label: 'Create VM Snapshot',
|
||||
dataView: true
|
||||
dataView: true,
|
||||
show: (record) => { return ['Running'].includes(record.state) }
|
||||
},
|
||||
{
|
||||
api: 'attachIso',
|
||||
icon: 'paper-clip',
|
||||
label: 'label.action.attach.iso',
|
||||
dataView: true,
|
||||
args: ['id', 'virtualmachineid']
|
||||
args: ['id', 'virtualmachineid'],
|
||||
show: (record) => { return !record.isoid }
|
||||
},
|
||||
{
|
||||
api: 'detachIso',
|
||||
icon: 'link',
|
||||
label: 'label.action.detach.iso',
|
||||
dataView: true,
|
||||
args: ['id', 'virtualmachineid']
|
||||
args: ['id', 'virtualmachineid'],
|
||||
show: (record) => { return 'isoid' in record && record.isoid }
|
||||
},
|
||||
{
|
||||
api: 'migrateVirtualMachine',
|
||||
icon: 'drag',
|
||||
label: 'label.migrate.instance.to.host',
|
||||
dataView: true,
|
||||
hidden: (record) => { return record.state !== 'Running' }
|
||||
show: (record) => { return ['Running'].includes(record.state) }
|
||||
},
|
||||
{
|
||||
api: 'migrateVirtualMachine',
|
||||
icon: 'drag',
|
||||
label: 'label.migrate.instance.to.ps',
|
||||
dataView: true,
|
||||
show: (record) => { return ['Stopped'].includes(record.state) }
|
||||
},
|
||||
{
|
||||
api: 'updateVMAffinityGroup',
|
||||
icon: 'swap',
|
||||
label: 'label.change.affinity',
|
||||
dataView: true,
|
||||
args: ['id', 'serviceofferingid'],
|
||||
show: (record) => { return ['Stopped'].includes(record.state) }
|
||||
},
|
||||
{
|
||||
api: 'changeServiceForVirtualMachine',
|
||||
icon: 'sliders',
|
||||
label: 'Change Service Offering',
|
||||
dataView: true,
|
||||
args: ['id', 'serviceofferingid'],
|
||||
show: (record) => { return ['Stopped'].includes(record.state) }
|
||||
},
|
||||
{
|
||||
api: 'resetPasswordForVirtualMachine',
|
||||
icon: 'key',
|
||||
label: 'Reset Instance Password',
|
||||
dataView: true,
|
||||
args: ['id']
|
||||
args: ['id'],
|
||||
show: (record) => { return ['Stopped'].includes(record.state) }
|
||||
},
|
||||
{
|
||||
api: 'resetSSHKeyForVirtualMachine',
|
||||
icon: 'lock',
|
||||
label: 'Reset SSH Key',
|
||||
dataView: true
|
||||
dataView: true,
|
||||
show: (record) => { return ['Stopped'].includes(record.state) }
|
||||
},
|
||||
{
|
||||
api: 'assignVirtualMachine',
|
||||
icon: 'user-add',
|
||||
label: 'Assign Instance to Another Account',
|
||||
dataView: true
|
||||
dataView: true,
|
||||
show: (record) => { return ['Stopped'].includes(record.state) }
|
||||
},
|
||||
{
|
||||
api: 'destroyVirtualMachine',
|
||||
|
|
@ -171,14 +185,14 @@ export default {
|
|||
icon: 'plus',
|
||||
label: 'Create SSH key pair',
|
||||
listView: true,
|
||||
args: ['name', 'publickey', 'domainid']
|
||||
args: ['name', 'account', 'domainid']
|
||||
},
|
||||
{
|
||||
api: 'deleteSSHKeyPair',
|
||||
icon: 'delete',
|
||||
label: 'Delete SSH key pair',
|
||||
dataView: true,
|
||||
args: ['name', 'domainid', 'account']
|
||||
args: ['name', 'account', 'domainid']
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -12,12 +12,33 @@ export default {
|
|||
columns: ['name', 'state', 'type', 'cidr', 'ip6cidr', 'broadcasturi', 'account', 'zonename'],
|
||||
details: ['name', 'id', 'description', 'type', 'traffictype', 'vpcid', 'vlan', 'broadcasturi', 'cidr', 'ip6cidr', 'netmask', 'gateway', 'ispersistent', 'restartrequired', 'reservediprange', 'redundantrouter', 'networkdomain', 'zonename', 'account', 'domain'],
|
||||
actions: [
|
||||
{
|
||||
api: 'createNetwork',
|
||||
icon: 'plus',
|
||||
label: 'Add Network',
|
||||
listView: true,
|
||||
popup: true,
|
||||
component: () => import('@/views/network/CreateNetwork.vue')
|
||||
},
|
||||
{
|
||||
api: 'updateNetwork',
|
||||
icon: 'edit',
|
||||
label: 'Update Network',
|
||||
dataView: true,
|
||||
args: ['id', 'name', 'displaytext', 'guestvmcidr']
|
||||
},
|
||||
{
|
||||
api: 'restartNetwork',
|
||||
icon: 'sync',
|
||||
label: 'Restart Network',
|
||||
dataView: true,
|
||||
args: ['id', 'makeredundant', 'cleanup']
|
||||
},
|
||||
{
|
||||
api: 'deleteNetwork',
|
||||
icon: 'delete',
|
||||
label: 'Delete Network',
|
||||
args: ['id'],
|
||||
listView: true,
|
||||
dataView: true
|
||||
}
|
||||
]
|
||||
|
|
@ -29,7 +50,37 @@ export default {
|
|||
permission: [ 'listVPCs' ],
|
||||
resourceType: 'Vpc',
|
||||
columns: ['name', 'state', 'displaytext', 'cidr', 'account', 'zonename'],
|
||||
details: ['name', 'id', 'displaytext', 'cidr', 'networkdomain', 'ispersistent', 'redundantvpcrouter', 'restartrequired', 'zonename', 'account', 'domain']
|
||||
details: ['name', 'id', 'displaytext', 'cidr', 'networkdomain', 'ispersistent', 'redundantvpcrouter', 'restartrequired', 'zonename', 'account', 'domain'],
|
||||
actions: [
|
||||
{
|
||||
api: 'createVPC',
|
||||
icon: 'plus',
|
||||
label: 'Add VPC',
|
||||
listView: true,
|
||||
args: ['name', 'displaytext', 'zoneid', 'cidr', 'networkdomain', 'vpcofferingid', 'start']
|
||||
},
|
||||
{
|
||||
api: 'updateVPC',
|
||||
icon: 'edit',
|
||||
label: 'Update VPC',
|
||||
dataView: true,
|
||||
args: ['id', 'name', 'displaytext']
|
||||
},
|
||||
{
|
||||
api: 'restartVPC',
|
||||
icon: 'sync',
|
||||
label: 'Restart VPC',
|
||||
dataView: true,
|
||||
args: ['id', 'makeredundant', 'cleanup']
|
||||
},
|
||||
{
|
||||
api: 'deleteVPC',
|
||||
icon: 'delete',
|
||||
label: 'Delete VPC',
|
||||
args: ['id'],
|
||||
dataView: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'securitygroups',
|
||||
|
|
@ -38,7 +89,24 @@ export default {
|
|||
permission: [ 'listSecurityGroups' ],
|
||||
resourceType: 'SecurityGroup',
|
||||
columns: ['name', 'description', 'account', 'domain'],
|
||||
details: ['name', 'id', 'description', 'account', 'domain']
|
||||
details: ['name', 'id', 'description', 'account', 'domain'],
|
||||
actions: [
|
||||
{
|
||||
api: 'createSecurityGroup',
|
||||
icon: 'plus',
|
||||
label: 'Add Security Group',
|
||||
listView: true,
|
||||
args: ['name', 'description']
|
||||
},
|
||||
{
|
||||
api: 'deleteSecurityGroup',
|
||||
icon: 'delete',
|
||||
label: 'Delete Security Group',
|
||||
args: ['id'],
|
||||
dataView: true,
|
||||
show: (record) => { return record.name !== 'default' }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'publicip',
|
||||
|
|
@ -46,8 +114,41 @@ export default {
|
|||
icon: 'environment',
|
||||
permission: [ 'listPublicIpAddresses' ],
|
||||
resourceType: 'PublicIpAddress',
|
||||
columns: ['ipaddress', 'state', 'associatednetworkname', 'virtualmachinename', 'allocated', 'account', 'zonename'],
|
||||
details: ['ipaddress', 'id', 'associatednetworkname', 'virtualmachinename', 'networkid', 'issourcenat', 'isstaticnat', 'virtualmachinename', 'vmipaddress', 'vlan', 'allocated', 'account', 'zonename']
|
||||
columns: ['ipaddress', 'state', 'issourcenat', 'associatednetworkname', 'virtualmachinename', 'allocated', 'account', 'zonename'],
|
||||
details: ['ipaddress', 'id', 'associatednetworkname', 'virtualmachinename', 'networkid', 'issourcenat', 'isstaticnat', 'virtualmachinename', 'vmipaddress', 'vlan', 'allocated', 'account', 'zonename'],
|
||||
actions: [
|
||||
{
|
||||
api: 'associateIpAddress',
|
||||
icon: 'plus',
|
||||
label: 'Acquire New IP',
|
||||
listView: true,
|
||||
args: ['networkid']
|
||||
},
|
||||
{
|
||||
api: 'enableStaticNat',
|
||||
icon: 'check-circle',
|
||||
label: 'Enable Static NAT',
|
||||
dataView: true,
|
||||
args: ['ipaddressid', 'virtualmachineid', 'vmguestip'],
|
||||
show: (record) => { return !record.virtualmachineid }
|
||||
},
|
||||
{
|
||||
api: 'disableStaticNat',
|
||||
icon: 'close-circle',
|
||||
label: 'Disable Static NAT',
|
||||
dataView: true,
|
||||
args: ['ipaddressid'],
|
||||
show: (record) => { return record.virtualmachineid }
|
||||
},
|
||||
{
|
||||
api: 'disassociateIpAddress',
|
||||
icon: 'delete',
|
||||
label: 'Delete IP',
|
||||
args: ['id'],
|
||||
dataView: true,
|
||||
show: (record) => { return !record.issourcenat }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'vpngateway',
|
||||
|
|
@ -56,7 +157,16 @@ export default {
|
|||
permission: [ 'listVpnCustomerGateways' ],
|
||||
resourceType: 'VpnGateway',
|
||||
columns: ['name', 'ipaddress', 'gateway', 'cidrlist', 'ipsecpsk', 'account', 'domain'],
|
||||
details: ['name', 'id', 'ipaddress', 'gateway', 'cidrlist', 'ipsecpsk', 'account', 'domain']
|
||||
details: ['name', 'id', 'ipaddress', 'gateway', 'cidrlist', 'ipsecpsk', 'account', 'domain'],
|
||||
actions: [
|
||||
{
|
||||
api: 'createVpnCustomerGateway',
|
||||
icon: 'plus',
|
||||
label: 'Add VPN Customer Gateway',
|
||||
listView: true,
|
||||
args: ['name', 'gateway', 'cidrlist', 'ipsecpsk', 'ikelifetime', 'esplifetime', 'dpd', 'forceencap', 'ikepolicy', 'esppolicy']
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ export default {
|
|||
permission: [ 'listVolumesMetrics', 'listVolumes' ],
|
||||
resourceType: 'Volume',
|
||||
columns: ['name', 'state', 'type', 'vmname', 'size', 'physicalsize', 'utilization', 'diskkbsread', 'diskkbswrite', 'diskiopstotal', 'storage', 'account', 'zonename'],
|
||||
hidden: ['storage', 'utilization'],
|
||||
details: ['name', 'id', 'type', 'deviceid', 'sizegb', 'physicalsize', 'provisioningtype', 'utilization', 'diskkbsread', 'diskkbswrite', 'diskioread', 'diskiowrite', 'diskiopstotal', 'path'],
|
||||
actions: [
|
||||
{
|
||||
|
|
@ -31,8 +30,9 @@ export default {
|
|||
api: 'getUploadParamsForVolume',
|
||||
icon: 'upload',
|
||||
label: 'Upload Local Volume',
|
||||
args: ['@file', 'name', 'zoneid', 'format', 'checksum'],
|
||||
listView: true
|
||||
listView: true,
|
||||
popup: true,
|
||||
component: () => import('@/views/storage/UploadLocalVolume.vue')
|
||||
},
|
||||
{
|
||||
api: 'attachVolume',
|
||||
|
|
@ -40,22 +40,31 @@ export default {
|
|||
label: 'Attach Volume',
|
||||
args: ['id', 'virtualmachineid'],
|
||||
dataView: true,
|
||||
hidden: (record) => { return record.virtualmachineid }
|
||||
show: (record) => { return !('virtualmachineid' in record) }
|
||||
},
|
||||
{
|
||||
api: 'detachVolume',
|
||||
icon: 'link',
|
||||
label: 'Detach Volume',
|
||||
args: ['id', 'virtualmachineid'],
|
||||
args: ['id'],
|
||||
dataView: true,
|
||||
hidden: (record) => { return !record.virtualmachineid }
|
||||
show: (record) => { return 'virtualmachineid' in record && record.virtualmachineid }
|
||||
},
|
||||
{
|
||||
api: 'migrateVolume',
|
||||
icon: 'drag',
|
||||
label: 'Migrate Volume',
|
||||
args: ['volumeid', 'storageid', 'livemigrate'],
|
||||
dataView: true
|
||||
api: 'createSnapshot',
|
||||
icon: 'camera',
|
||||
label: 'Take Snapshot',
|
||||
args: ['volumeid', 'name', 'asyncbackup', 'tags'],
|
||||
dataView: true,
|
||||
show: (record) => { return record.state === 'Ready' }
|
||||
},
|
||||
{
|
||||
api: 'createSnapshotPolicy',
|
||||
icon: 'video-camera',
|
||||
label: 'Recurring Snapshots',
|
||||
args: ['volumeid', 'schedule', 'timezone', 'intervaltype', 'maxsnaps'],
|
||||
dataView: true,
|
||||
show: (record) => { return record.state === 'Ready' }
|
||||
},
|
||||
{
|
||||
api: 'resizeVolume',
|
||||
|
|
@ -65,6 +74,14 @@ export default {
|
|||
args: ['id', 'virtualmachineid'],
|
||||
dataView: true
|
||||
},
|
||||
{
|
||||
api: 'migrateVolume',
|
||||
icon: 'drag',
|
||||
label: 'Migrate Volume',
|
||||
args: ['volumeid', 'storageid', 'livemigrate'],
|
||||
dataView: true,
|
||||
show: (record) => { return 'virtualmachineid' in record && record.virtualmachineid }
|
||||
},
|
||||
{
|
||||
api: 'extractVolume',
|
||||
icon: 'cloud-download',
|
||||
|
|
@ -77,6 +94,14 @@ export default {
|
|||
},
|
||||
dataView: true
|
||||
},
|
||||
{
|
||||
api: 'createTemplate',
|
||||
icon: 'picture',
|
||||
label: 'Create Template from Volume',
|
||||
args: ['volumeid', 'name', 'displaytext', 'ostypeid', 'ispublic', 'isfeatured', 'isdynamicallyscalable', 'requireshvm', 'passwordenabled', 'sshkeyenabled'],
|
||||
dataView: true,
|
||||
show: (record) => { return record.type === 'ROOT' }
|
||||
},
|
||||
{
|
||||
api: 'deleteVolume',
|
||||
icon: 'delete',
|
||||
|
|
@ -101,27 +126,28 @@ export default {
|
|||
icon: 'plus',
|
||||
label: 'Create volume',
|
||||
dataView: true,
|
||||
args: [
|
||||
'name', 'snapshotid', 'diskofferingid', 'size'
|
||||
]
|
||||
args: ['snapshotid', 'name']
|
||||
},
|
||||
{
|
||||
api: 'createTemplate',
|
||||
icon: 'picture',
|
||||
label: 'Create volume',
|
||||
dataView: true,
|
||||
args: ['snapshotid', 'name', 'displaytext', 'ostypeid', 'ispublic', 'isfeatured', 'isdynamicallyscalable', 'requireshvm', 'passwordenabled', 'sshkeyenabled']
|
||||
},
|
||||
{
|
||||
api: 'revertSnapshot',
|
||||
icon: 'revert',
|
||||
label: 'Revert snapshot',
|
||||
icon: 'sync',
|
||||
label: 'Revert Snapshot',
|
||||
dataView: true,
|
||||
args: [
|
||||
'id'
|
||||
]
|
||||
args: ['id']
|
||||
},
|
||||
{
|
||||
api: 'deleteSnapshot',
|
||||
icon: 'delete',
|
||||
label: 'Delete snapshot',
|
||||
label: 'Delete Snapshot',
|
||||
dataView: true,
|
||||
args: [
|
||||
'id'
|
||||
]
|
||||
args: ['id']
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -136,21 +162,17 @@ export default {
|
|||
actions: [
|
||||
{
|
||||
api: 'revertToVMSnapshot',
|
||||
icon: 'revert',
|
||||
icon: 'sync',
|
||||
label: 'Revert VM snapshot',
|
||||
dataView: true,
|
||||
args: [
|
||||
'vmsnapshotid'
|
||||
]
|
||||
args: ['vmsnapshotid']
|
||||
},
|
||||
{
|
||||
api: 'deleteVMSnapshot',
|
||||
icon: 'delete',
|
||||
label: 'Delete VM Snapshot',
|
||||
dataView: true,
|
||||
args: [
|
||||
'vmsnapshotid'
|
||||
]
|
||||
args: ['vmsnapshotid']
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,20 +5,27 @@
|
|||
</a-card>
|
||||
<a-row>
|
||||
<a-col :span="18">
|
||||
<a-tooltip placement="bottom" v-for="(action, actionIndex) in actions" :key="actionIndex" v-if="action.api in $store.getters.apis && ((!dataView && (action.listView || action.groupAction && selectedRowKeys.length > 0)) || (dataView && action.dataView))">
|
||||
<template slot="title">
|
||||
{{ $t(action.label) }}
|
||||
</template>
|
||||
<a-button
|
||||
:icon="action.icon"
|
||||
:type="action.icon === 'delete' ? 'danger' : (action.icon === 'plus' ? 'primary' : 'default')"
|
||||
shape="circle"
|
||||
style="margin-right: 5px"
|
||||
@click="execAction(action)"
|
||||
:disabled="'hidden' in action ? dataView && action.hidden(resource) : false"
|
||||
>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<span
|
||||
v-for="(action, actionIndex) in actions"
|
||||
:key="actionIndex">
|
||||
<a-tooltip
|
||||
placement="bottom"
|
||||
v-if="action.api in $store.getters.apis &&
|
||||
((!dataView && (action.listView || action.groupAction && selectedRowKeys.length > 0)) ||
|
||||
(dataView && action.dataView && ('show' in action ? action.show(resource) : true)))">
|
||||
<template slot="title">
|
||||
{{ $t(action.label) }}
|
||||
</template>
|
||||
<a-button
|
||||
:icon="action.icon"
|
||||
:type="action.icon === 'delete' ? 'danger' : (action.icon === 'plus' ? 'primary' : 'default')"
|
||||
shape="circle"
|
||||
style="margin-right: 5px"
|
||||
@click="execAction(action)"
|
||||
>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<span v-if="!dataView" style="float: right; padding-right: 8px; margin-top: -2px">
|
||||
<a-tooltip placement="bottom">
|
||||
<template slot="title">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
|
||||
TODO: create network form: L2, isolated and shared
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<div>
|
||||
TODO: upload volume from local file component
|
||||
|
||||
<a-upload
|
||||
name="avatar"
|
||||
listType="picture-card"
|
||||
class="avatar-uploader"
|
||||
:showUploadList="false"
|
||||
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
|
||||
:beforeUpload="beforeUpload"
|
||||
@change="handleChange"
|
||||
>
|
||||
<img v-if="imageUrl" :src="imageUrl" alt="avatar" />
|
||||
<div v-else>
|
||||
<a-icon :type="loading ? 'loading' : 'plus'" />
|
||||
<div class="ant-upload-text">Upload Volume</div>
|
||||
</div>
|
||||
</a-upload>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: '',
|
||||
components: {
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: '',
|
||||
components: {
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
Loading…
Reference in New Issue