feat: Add basic import screen (#1056)

Co-authored-by: Chris Hunt <chris.hunt@kasmweb.com>
This commit is contained in:
Attila Kerekes 2022-12-04 21:12:54 +00:00 committed by GitHub
parent 45cc84c99c
commit 8f9b2959b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 128 additions and 48 deletions

2
public/css/app.css vendored

File diff suppressed because one or more lines are too long

2
public/js/app.js vendored

File diff suppressed because one or more lines are too long

View file

@ -483,10 +483,14 @@ var icons = {
"arrow-rotate-right": [512, 512, [8635, "arrow-right-rotate", "arrow-rotate-forward", "redo"], "f01e", "M496 48V192c0 17.69-14.31 32-32 32H320c-17.69 0-32-14.31-32-32s14.31-32 32-32h63.39c-29.97-39.7-77.25-63.78-127.6-63.78C167.7 96.22 96 167.9 96 256s71.69 159.8 159.8 159.8c34.88 0 68.03-11.03 95.88-31.94c14.22-10.53 34.22-7.75 44.81 6.375c10.59 14.16 7.75 34.22-6.375 44.81c-39.03 29.28-85.36 44.86-134.2 44.86C132.5 479.9 32 379.4 32 256s100.5-223.9 223.9-223.9c69.15 0 134 32.47 176.1 86.12V48c0-17.69 14.31-32 32-32S496 30.31 496 48z"],
"ban": [512, 512, [], "f05e", "M256 8C119.034 8 8 119.033 8 256s111.034 248 248 248 248-111.034 248-248S392.967 8 256 8zm130.108 117.892c65.448 65.448 70 165.481 20.677 235.637L150.47 105.216c70.204-49.356 170.226-44.735 235.638 20.676zM125.892 386.108c-65.448-65.448-70-165.481-20.677-235.637L361.53 406.784c-70.203 49.356-170.226 44.736-235.638-20.676z"],
"check": [512, 512, [], "f00c", "M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"],
"circle-check": [512, 512, [61533, "check-circle"], "f058", ["M0 256C0 114.6 114.6 0 256 0C397.4 0 512 114.6 512 256C512 397.4 397.4 512 256 512C114.6 512 0 397.4 0 256zM371.8 211.8C382.7 200.9 382.7 183.1 371.8 172.2C360.9 161.3 343.1 161.3 332.2 172.2L224 280.4L179.8 236.2C168.9 225.3 151.1 225.3 140.2 236.2C129.3 247.1 129.3 264.9 140.2 275.8L204.2 339.8C215.1 350.7 232.9 350.7 243.8 339.8L371.8 211.8z", "M371.8 172.2C382.7 183.1 382.7 200.9 371.8 211.8L243.8 339.8C232.9 350.7 215.1 350.7 204.2 339.8L140.2 275.8C129.3 264.9 129.3 247.1 140.2 236.2C151.1 225.3 168.9 225.3 179.8 236.2L224 280.4L332.2 172.2C343.1 161.3 360.9 161.3 371.8 172.2V172.2z"]],
"circle-xmark": [512, 512, [61532, "times-circle", "xmark-circle"], "f057", ["M0 256C0 114.6 114.6 0 256 0C397.4 0 512 114.6 512 256C512 397.4 397.4 512 256 512C114.6 512 0 397.4 0 256zM175 208.1L222.1 255.1L175 303C165.7 312.4 165.7 327.6 175 336.1C184.4 346.3 199.6 346.3 208.1 336.1L255.1 289.9L303 336.1C312.4 346.3 327.6 346.3 336.1 336.1C346.3 327.6 346.3 312.4 336.1 303L289.9 255.1L336.1 208.1C346.3 199.6 346.3 184.4 336.1 175C327.6 165.7 312.4 165.7 303 175L255.1 222.1L208.1 175C199.6 165.7 184.4 165.7 175 175C165.7 184.4 165.7 199.6 175 208.1V208.1z", "M255.1 222.1L303 175C312.4 165.7 327.6 165.7 336.1 175C346.3 184.4 346.3 199.6 336.1 208.1L289.9 255.1L336.1 303C346.3 312.4 346.3 327.6 336.1 336.1C327.6 346.3 312.4 346.3 303 336.1L255.1 289.9L208.1 336.1C199.6 346.3 184.4 346.3 175 336.1C165.7 327.6 165.7 312.4 175 303L222.1 255.1L175 208.1C165.7 199.6 165.7 184.4 175 175C184.4 165.7 199.6 165.7 208.1 175L255.1 222.1z"]],
"cloud-download": [640, 512, [], "f0ed", "M537.6 226.6c4.1-10.7 6.4-22.4 6.4-34.6 0-53-43-96-96-96-19.7 0-38.1 6-53.3 16.2C367 64.2 315.3 32 256 32c-88.4 0-160 71.6-160 160 0 2.7.1 5.4.2 8.1C40.2 219.8 0 273.2 0 336c0 79.5 64.5 144 144 144h368c70.7 0 128-57.3 128-128 0-61.9-44-113.6-102.4-125.4zm-139.9 93L305 412.3c-9.4 9.4-24.6 9.4-33.9 0l-92.7-92.7c-9.4-9.4-9.4-24.6 0-33.9l10.8-10.8c9.6-9.6 25.2-9.3 34.5.5l32.4 34.5V184c0-13.3 10.7-24 24-24h16c13.3 0 24 10.7 24 24v125.9l32.4-34.5c9.3-9.9 24.9-10.1 34.5-.5l10.8 10.8c9.2 9.3 9.2 24.5-.1 33.9z"],
"cogs": [640, 512, [], "f085", "M512.1 191l-8.2 14.3c-3 5.3-9.4 7.5-15.1 5.4-11.8-4.4-22.6-10.7-32.1-18.6-4.6-3.8-5.8-10.5-2.8-15.7l8.2-14.3c-6.9-8-12.3-17.3-15.9-27.4h-16.5c-6 0-11.2-4.3-12.2-10.3-2-12-2.1-24.6 0-37.1 1-6 6.2-10.4 12.2-10.4h16.5c3.6-10.1 9-19.4 15.9-27.4l-8.2-14.3c-3-5.2-1.9-11.9 2.8-15.7 9.5-7.9 20.4-14.2 32.1-18.6 5.7-2.1 12.1.1 15.1 5.4l8.2 14.3c10.5-1.9 21.2-1.9 31.7 0L552 6.3c3-5.3 9.4-7.5 15.1-5.4 11.8 4.4 22.6 10.7 32.1 18.6 4.6 3.8 5.8 10.5 2.8 15.7l-8.2 14.3c6.9 8 12.3 17.3 15.9 27.4h16.5c6 0 11.2 4.3 12.2 10.3 2 12 2.1 24.6 0 37.1-1 6-6.2 10.4-12.2 10.4h-16.5c-3.6 10.1-9 19.4-15.9 27.4l8.2 14.3c3 5.2 1.9 11.9-2.8 15.7-9.5 7.9-20.4 14.2-32.1 18.6-5.7 2.1-12.1-.1-15.1-5.4l-8.2-14.3c-10.4 1.9-21.2 1.9-31.7 0zm-10.5-58.8c38.5 29.6 82.4-14.3 52.8-52.8-38.5-29.7-82.4 14.3-52.8 52.8zM386.3 286.1l33.7 16.8c10.1 5.8 14.5 18.1 10.5 29.1-8.9 24.2-26.4 46.4-42.6 65.8-7.4 8.9-20.2 11.1-30.3 5.3l-29.1-16.8c-16 13.7-34.6 24.6-54.9 31.7v33.6c0 11.6-8.3 21.6-19.7 23.6-24.6 4.2-50.4 4.4-75.9 0-11.5-2-20-11.9-20-23.6V418c-20.3-7.2-38.9-18-54.9-31.7L74 403c-10 5.8-22.9 3.6-30.3-5.3-16.2-19.4-33.3-41.6-42.2-65.7-4-10.9.4-23.2 10.5-29.1l33.3-16.8c-3.9-20.9-3.9-42.4 0-63.4L12 205.8c-10.1-5.8-14.6-18.1-10.5-29 8.9-24.2 26-46.4 42.2-65.8 7.4-8.9 20.2-11.1 30.3-5.3l29.1 16.8c16-13.7 34.6-24.6 54.9-31.7V57.1c0-11.5 8.2-21.5 19.6-23.5 24.6-4.2 50.5-4.4 76-.1 11.5 2 20 11.9 20 23.6v33.6c20.3 7.2 38.9 18 54.9 31.7l29.1-16.8c10-5.8 22.9-3.6 30.3 5.3 16.2 19.4 33.2 41.6 42.1 65.8 4 10.9.1 23.2-10 29.1l-33.7 16.8c3.9 21 3.9 42.5 0 63.5zm-117.6 21.1c59.2-77-28.7-164.9-105.7-105.7-59.2 77 28.7 164.9 105.7 105.7zm243.4 182.7l-8.2 14.3c-3 5.3-9.4 7.5-15.1 5.4-11.8-4.4-22.6-10.7-32.1-18.6-4.6-3.8-5.8-10.5-2.8-15.7l8.2-14.3c-6.9-8-12.3-17.3-15.9-27.4h-16.5c-6 0-11.2-4.3-12.2-10.3-2-12-2.1-24.6 0-37.1 1-6 6.2-10.4 12.2-10.4h16.5c3.6-10.1 9-19.4 15.9-27.4l-8.2-14.3c-3-5.2-1.9-11.9 2.8-15.7 9.5-7.9 20.4-14.2 32.1-18.6 5.7-2.1 12.1.1 15.1 5.4l8.2 14.3c10.5-1.9 21.2-1.9 31.7 0l8.2-14.3c3-5.3 9.4-7.5 15.1-5.4 11.8 4.4 22.6 10.7 32.1 18.6 4.6 3.8 5.8 10.5 2.8 15.7l-8.2 14.3c6.9 8 12.3 17.3 15.9 27.4h16.5c6 0 11.2 4.3 12.2 10.3 2 12 2.1 24.6 0 37.1-1 6-6.2 10.4-12.2 10.4h-16.5c-3.6 10.1-9 19.4-15.9 27.4l8.2 14.3c3 5.2 1.9 11.9-2.8 15.7-9.5 7.9-20.4 14.2-32.1 18.6-5.7 2.1-12.1-.1-15.1-5.4l-8.2-14.3c-10.4 1.9-21.2 1.9-31.7 0zM501.6 431c38.5 29.6 82.4-14.3 52.8-52.8-38.5-29.6-82.4 14.3-52.8 52.8z"],
"edit": [576, 512, [], "f044", "M402.6 83.2l90.2 90.2c3.8 3.8 3.8 10 0 13.8L274.4 405.6l-92.8 10.3c-12.4 1.4-22.9-9.1-21.5-21.5l10.3-92.8L388.8 83.2c3.8-3.8 10-3.8 13.8 0zm162-22.9l-48.8-48.8c-15.2-15.2-39.9-15.2-55.2 0l-35.4 35.4c-3.8 3.8-3.8 10 0 13.8l90.2 90.2c3.8 3.8 10 3.8 13.8 0l35.4-35.4c15.2-15.3 15.2-40 0-55.2zM384 346.2V448H64V128h229.8c3.2 0 6.2-1.3 8.5-3.5l40-40c7.6-7.6 2.2-20.5-8.5-20.5H48C21.5 64 0 85.5 0 112v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V306.2c0-10.7-12.9-16-20.5-8.5l-40 40c-2.2 2.3-3.5 5.3-3.5 8.5z"],
"exchange": [512, 512, [], "f0ec", "M0 168v-16c0-13.255 10.745-24 24-24h381.97l-30.467-27.728c-9.815-9.289-10.03-24.846-.474-34.402l10.84-10.84c9.373-9.373 24.568-9.373 33.941 0l82.817 82.343c12.497 12.497 12.497 32.758 0 45.255l-82.817 82.343c-9.373 9.373-24.569 9.373-33.941 0l-10.84-10.84c-9.556-9.556-9.341-25.114.474-34.402L405.97 192H24c-13.255 0-24-10.745-24-24zm488 152H106.03l30.467-27.728c9.815-9.289 10.03-24.846.474-34.402l-10.84-10.84c-9.373-9.373-24.568-9.373-33.941 0L9.373 329.373c-12.497 12.497-12.497 32.758 0 45.255l82.817 82.343c9.373 9.373 24.569 9.373 33.941 0l10.84-10.84c9.556-9.556 9.341-25.113-.474-34.402L106.03 384H488c13.255 0 24-10.745 24-24v-16c0-13.255-10.745-24-24-24z"],
"file-arrow-down": [384, 512, ["file-download"], "f56d", ["M256 128V0H48C21.49 0 0 21.49 0 48v416C0 490.5 21.49 512 48 512h288c26.51 0 48-21.49 48-48V128H256zM288.1 360.1l-80 80c-9.375 9.375-24.56 9.375-33.94 0l-80-80c-9.375-9.375-9.375-24.56 0-33.94C99.72 322.3 105.8 320 112 320s12.28 2.344 16.97 7.031L168 366.1V248C168 234.8 178.8 224 192 224s24 10.75 24 24v118.1l39.03-39.03c9.375-9.375 24.56-9.375 33.94 0S298.3 351.6 288.1 360.1z", "M256 0v128h128L256 0zM255 327L216 366.1V248C216 234.8 205.3 224 192 224S168 234.8 168 248v118.1l-39.03-39.03C124.3 322.3 118.2 320 112 320s-12.28 2.344-16.97 7.031c-9.375 9.375-9.375 24.56 0 33.94l80 80c9.375 9.375 24.56 9.375 33.94 0l80-80c9.375-9.375 9.375-24.56 0-33.94S264.4 317.7 255 327z"]],
"file-arrow-up": [384, 512, ["file-upload"], "f574", ["M256 128V0H48C21.49 0 0 21.49 0 48v416C0 490.5 21.49 512 48 512h288c26.51 0 48-21.49 48-48V128H256zM288.1 344.1C284.3 349.7 278.2 352 272 352s-12.28-2.344-16.97-7.031L216 305.9V424c0 13.25-10.75 24-24 24s-24-10.75-24-24V305.9l-39.03 39.03c-9.375 9.375-24.56 9.375-33.94 0s-9.375-24.56 0-33.94l80-80c9.375-9.375 24.56-9.375 33.94 0l80 80C298.3 320.4 298.3 335.6 288.1 344.1z", "M256 0v128h128L256 0zM208.1 231c-9.375-9.375-24.56-9.375-33.94 0l-80 80c-9.375 9.375-9.375 24.56 0 33.94s24.56 9.375 33.94 0L168 305.9V424C168 437.3 178.8 448 192 448s24-10.75 24-24V305.9l39.03 39.03C259.7 349.7 265.8 352 272 352s12.28-2.344 16.97-7.031c9.375-9.375 9.375-24.56 0-33.94L208.1 231z"]],
"list": [512, 512, [], "f03a", "M128 116V76c0-8.837 7.163-16 16-16h352c8.837 0 16 7.163 16 16v40c0 8.837-7.163 16-16 16H144c-8.837 0-16-7.163-16-16zm16 176h352c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H144c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h352c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H144c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zM16 144h64c8.837 0 16-7.163 16-16V64c0-8.837-7.163-16-16-16H16C7.163 48 0 55.163 0 64v64c0 8.837 7.163 16 16 16zm0 160h64c8.837 0 16-7.163 16-16v-64c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v64c0 8.837 7.163 16 16 16zm0 160h64c8.837 0 16-7.163 16-16v-64c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v64c0 8.837 7.163 16 16 16z"],
"pencil": [512, 512, [], "f040", "M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"],
"plus": [448, 512, [], "f067", "M416 208H272V64c0-17.67-14.33-32-32-32h-32c-17.67 0-32 14.33-32 32v144H32c-17.67 0-32 14.33-32 32v32c0 17.67 14.33 32 32 32h144v144c0 17.67 14.33 32 32 32h32c17.67 0 32-14.33 32-32V304h144c17.67 0 32-14.33 32-32v-32c0-17.67-14.33-32-32-32z"],

View file

@ -1,4 +1,4 @@
{
"/css/app.css": "/css/app.css?id=9a25947db63214edd4e6f459200dfa62",
"/js/app.js": "/js/app.js?id=50647209eddf7eb990cfe03fcc6652ed"
"/css/app.css": "/css/app.css?id=910562b0b11f9731ff1cdded5e57f7b5",
"/js/app.js": "/js/app.js?id=455ff98b9cf4ee09f73e8521270cea96"
}

View file

@ -3,33 +3,27 @@ const APP_LOAD_URL = "appload";
/**
*
* @param {string|null} appId
* @returns {Promise<{}>|Promise<any>}
* @param {object} item
* @param {array} errors
*/
const fetchAppDetails = (appId) => {
if (appId === null) {
return Promise.resolve({});
const updateStatus = ({ item, errors }) => {
console.log(item, errors);
let statusLine;
if (errors.length === 0) {
statusLine = `<li class="success"><i class="fas fa-circle-check"></i> Imported: ${item.title} </li>`;
} else {
statusLine = `<li class="fail"><i class="fas fa-circle-xmark"></i> Failed: ${item.title} - ${errors[0]} </li>`;
}
return fetch(APP_LOAD_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ app: appId }),
})
.then((response) => response.json())
.catch(() => ({}));
document.querySelector(".import-status").innerHTML += statusLine;
};
/**
*
* @returns {string}
*/
const getCSRFToken = () => {
const tokenSelector = 'input[name="_token"]';
return document.querySelector(tokenSelector).value;
};
function clearStatus() {
const statusContainer = document.querySelector(".import-status");
statusContainer.innerHTML = "";
}
/**
*
@ -48,11 +42,20 @@ const postToApi = (data, csrfToken) =>
body: JSON.stringify(data),
});
/**
*
* @returns {string}
*/
const getCSRFToken = () => {
const tokenSelector = 'input[name="_token"]';
return document.querySelector(tokenSelector).value;
};
/**
*
* @param {object} item
* @param {object} appDetails
* @returns {undefined}
* @returns {object}
*/
const mergeItemWithAppDetails = (item, appDetails) => ({
pinned: 1,
@ -72,21 +75,51 @@ const mergeItemWithAppDetails = (item, appDetails) => ({
config: item.description ? JSON.parse(item.description) : null,
});
/**
*
* @param {string|null} appId
* @returns {Promise<{}>|Promise<any>}
*/
const fetchAppDetails = (appId) => {
if (appId === null || appId === "null") {
return Promise.resolve({});
}
return fetch(APP_LOAD_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ app: appId }),
}).then((response) => response.json());
};
/**
*
* @param {array} items
*/
const importItems = (items) => {
items.forEach((item) => {
const errors = [];
fetchAppDetails(item.appid)
.catch(() =>
errors.push(new Error(`Failed to find app id: ${item.appid}`))
)
.then((appDetails) => {
const itemWithAppDetails = mergeItemWithAppDetails(item, appDetails);
const csrfToken = getCSRFToken();
return postToApi(itemWithAppDetails, csrfToken);
})
.then((response) => {
console.log(response);
.catch(() =>
errors.push(new Error(`Failed to create item: ${item.title}`))
)
.finally(() => {
updateStatus({
item,
errors,
});
});
});
};
@ -97,32 +130,47 @@ const importItems = (items) => {
* @returns {Promise<unknown>}
*/
const readJSON = (file) =>
new Promise((resolve) => {
const reader = new FileReader();
new Promise((resolve, reject) => {
try {
const reader = new FileReader();
reader.onload = (e) => {
const contents = e.target.result;
resolve(JSON.parse(contents));
};
reader.onload = (event) => {
const contents = event.target.result;
resolve(JSON.parse(contents));
};
reader.readAsText(file);
reader.readAsText(file);
} catch (e) {
reject(new Error("Unable to read file"));
}
});
/**
*
* @param {Event} event
*/
const openFileForImport = (event) => {
const file = event.target.files[0];
if (!file) {
return;
}
const openFileForImport = (file) => {
clearStatus();
readJSON(file).then(importItems);
return readJSON(file)
.catch((error) => {
console.error(error);
})
.then(importItems);
};
const fileInput = document.querySelector("input[name='import']");
const importButton = document.querySelectorAll(".import-button");
if (fileInput) {
if (fileInput && importButton) {
importButton.forEach((importButton) => {
importButton.addEventListener("click", () => {
const file = fileInput.files[0];
if (!file) {
return;
}
openFileForImport(file);
});
});
fileInput.addEventListener("change", openFileForImport, false);
}

View file

@ -1141,4 +1141,22 @@ select:-webkit-autofill:focus {
a:not(:focus-within) {
opacity: 0.4;
}
}
.import-status {
list-style: none;
li {
display: flex;
align-items: center;
margin: 6px 0;
svg {
margin-right: 8px;
}
&.success svg {
color: $app-green;
}
&.fail svg {
color: $app-red;
}
}
}

View file

@ -72,6 +72,7 @@ return array (
'apps.only_admin_account' => 'Only if you have admin-account!',
'apps.autologin_url' => 'Auto login url',
'apps.show_deleted' => 'Showing Deleted Applications',
'app.import' => 'Import',
'dashboard' => 'Home dashboard',
'user.user_list' => 'Users',
'user.add_user' => 'Add user',

View file

@ -4,9 +4,9 @@
<section class="module-container">
<header>
<div class="section-title">{{ __('import.title') }}</div>
<div class="section-title">{{ __('app.import') }}</div>
<div class="module-actions">
<button type="submit"class="button"><i class="fa fa-save"></i><span>{{ __('import.save') }}</span></button>
<button type="submit" class="button import-button"><i class="fa fa-save"></i><span>{{ __('import') }}</span></button>
<a href="{{ route('settings.index', []) }}" class="button"><i class="fa fa-ban"></i><span>{{ __('app.buttons.cancel') }}</span></a>
</div>
</header>
@ -16,12 +16,18 @@
<div class="input">
<input class="form-control" name="import" type="file">
</div>
<div>
<ul class="import-status" style="display: block">
</ul>
</div>
</div>
<footer>
<div class="section-title">&nbsp;</div>
<div class="module-actions">
<button type="submit"class="button"><i class="fa fa-save"></i><span>{{ __('import.save') }}</span></button>
<button type="submit" class="button import-button"><i class="fa fa-save"></i><span>{{ __('import') }}</span></button>
<a href="{{ route('settings.index', []) }}" class="button"><i class="fa fa-ban"></i><span>{{ __('app.buttons.cancel') }}</span></a>
</div>
</footer>

View file

@ -11,8 +11,6 @@
</div>
<div class="module-actions">
{{-- <a href="import" id="item-import" class="button"><i class="fa fa-upload"></i><span>{{ __('import') }}</span></a>--}}
<a href="#export" id="item-export" class="button"><i class="fa fa-download"></i><span>{{ __('export') }}</span></a>
<a href="{{ route('applist', []) }}" class="button"><i class="fa fa-cloud-download"></i><span>{{ __('app.buttons.downloadapps') }}</span></a>
<a href="{{ route('items.create', []) }}" title="" class="button"><i class="fa fa-plus"></i><span>{{ __('app.buttons.add') }}</span></a>
<a href="{{ route('dash', []) }}" class="button"><i class="fa fa-ban"></i><span>{{ __('app.buttons.cancel') }}</span></a>

View file

@ -2,13 +2,18 @@
@section('content')
@foreach ($groups as $group)
@foreach ($groups as $index => $group)
<section class="module-container">
<header>
<div class="section-title">
{{ __($group->title) }}
</div>
@if($index === 0)
<div class="module-actions">
<a href="{{ route('items.import', []) }}" id="item-import" class="button"><i class="fas fa-file-arrow-up"></i><span>{{ __('import') }}</span></a>
<a href="#export" id="item-export" class="button"><i class="fas fa-file-arrow-down"></i><span>{{ __('export') }}</span></a>
</div>
@endif
</header>
<table class="table table-hover">