"));
}
if (this.paginationParts.includes('pageInfo') || this.paginationParts.includes('pageInfoShort')) {
- var totalRows = this.options.totalRows + (this.options.sidePagination === 'client' && this.options.paginationLoadMore && !this._paginationLoaded ? ' +' : '');
+ var totalRows = this.options.totalRows;
+ if (this.options.sidePagination === 'client' && this.options.paginationLoadMore && !this._paginationLoaded && this.totalPages > 1) {
+ totalRows += ' +';
+ }
var paginationInfo = this.paginationParts.includes('pageInfoShort') ? opts.formatDetailPagination(totalRows) : opts.formatShowingRows(this.pageFrom, this.pageTo, totalRows, opts.totalNotFiltered);
html.push(""));
}
@@ -9686,14 +9722,14 @@
data_["data-".concat(k)] = _typeof(v) === 'object' ? JSON.stringify(v) : v;
}
}
- var tr = Utils.h('tr', _objectSpread2(_objectSpread2({}, attributes), {}, {
+ var tr = Utils.h('tr', _objectSpread2(_objectSpread2({
id: Array.isArray(item) ? undefined : item._id,
class: style && style.classes || (Array.isArray(item) ? undefined : item._class),
style: style && style.css || (Array.isArray(item) ? undefined : item._style),
'data-index': i,
'data-uniqueid': Utils.getItemField(item, this.options.uniqueId, false),
'data-has-detail-view': this.options.detailView && Utils.calculateObjectValue(null, this.options.detailFilter, [i, item]) ? 'true' : undefined
- }, data_));
+ }, attributes), data_));
var trChildren = [];
var detailViewTemplate = '';
if (Utils.hasDetailViewIcon(this.options)) {
@@ -9717,6 +9753,7 @@
class: _this7.header.classes[j] ? [_this7.header.classes[j]] : [],
style: _this7.header.styles[j] ? [_this7.header.styles[j]] : []
};
+ var cardViewClass = "card-view card-view-field-".concat(field);
if ((_this7.fromHtml || _this7.autoMergeCells) && typeof value_ === 'undefined') {
if (!column.checkbox && !column.radio) {
return;
@@ -9731,15 +9768,15 @@
// handle class, style, id, rowspan, colspan and title of td
for (var _i10 = 0, _arr = ['class', 'style', 'id', 'rowspan', 'colspan', 'title']; _i10 < _arr.length; _i10++) {
- var _item = _arr[_i10];
- var _value = _item["_".concat(field, "_").concat(_item)];
+ var attr = _arr[_i10];
+ var _value = item["_".concat(field, "_").concat(attr)];
if (!_value) {
continue;
}
- if (attrs[_item]) {
- attrs[_item].push(_value);
+ if (attrs[attr]) {
+ attrs[attr].push(_value);
} else {
- attrs[_item] = _value;
+ attrs[attr] = _value;
}
}
var cellStyle = Utils.calculateObjectValue(_this7.header, _this7.header.cellStyles[j], [value_, item, i, field], {});
@@ -9784,7 +9821,7 @@
var valueNodes = _this7.header.formatters[j] && (typeof value === 'string' || value instanceof Node || value instanceof $) ? Utils.htmlToNodes(value) : [];
item[_this7.header.stateField] = value === true || !!value_ || value && value.checked;
return Utils.h(_this7.options.cardView ? 'div' : 'td', {
- class: [_this7.options.cardView ? 'card-view' : 'bs-checkbox', column.class],
+ class: [_this7.options.cardView ? cardViewClass : 'bs-checkbox', column.class],
style: _this7.options.cardView ? undefined : attrs.style
}, [Utils.h('label', {}, [Utils.h('input', {
'data-index': i,
@@ -9798,7 +9835,7 @@
if (_this7.options.cardView) {
if (_this7.options.smartDisplay && value === '') {
return Utils.h('div', {
- class: 'card-view'
+ class: cardViewClass
});
}
var cardTitle = _this7.options.showHeader ? Utils.h('span', {
@@ -9807,7 +9844,7 @@
html: Utils.getFieldTitle(_this7.columns, field)
}) : '';
return Utils.h('div', {
- class: 'card-view'
+ class: cardViewClass
}, [cardTitle, Utils.h('span', {
class: ['card-view-value', cellStyle.classes],
style: attrs.style
@@ -10452,6 +10489,7 @@
if (Utils.compareObjects(this.options, options, true)) {
return;
}
+ this.optionsColumnsChanged = !!options.columns;
this.options = Utils.extend(this.options, options);
this.trigger('refresh-options', this.options);
this.destroy();
@@ -10591,6 +10629,10 @@
}
var row = this.data[params.index];
var originalIndex = this.options.data.indexOf(row);
+ if (originalIndex === -1) {
+ this.append([params.row]);
+ return;
+ }
this.data.splice(params.index, 0, params.row);
this.options.data.splice(originalIndex, 0, params.row);
this.initSearch();
@@ -11109,6 +11151,7 @@
}, {
key: "destroy",
value: function destroy() {
+ clearTimeout(this.timeoutId_);
this.$el.insertBefore(this.$container);
$(this.options.toolbar).insertBefore(this.$el);
this.$container.next().remove();
@@ -11239,7 +11282,7 @@
}, {
key: "filterBy",
value: function filterBy(columns, options) {
- this.filterOptions = Utils.isEmptyObject(options) ? this.options.filterOptions : Utils.extend(this.options.filterOptions, options);
+ this.filterOptions = Utils.isEmptyObject(options) ? this.options.filterOptions : Utils.extend({}, this.options.filterOptions, options);
this.filterColumns = Utils.isEmptyObject(columns) ? {} : columns;
this.options.pageNumber = 1;
this.initSearch();
@@ -11458,6 +11501,7 @@
$.fn.bootstrapTable.Constructor = BootstrapTable;
$.fn.bootstrapTable.theme = Constants.THEME;
$.fn.bootstrapTable.VERSION = Constants.VERSION;
+ $.fn.bootstrapTable.icons = Constants.ICONS;
$.fn.bootstrapTable.defaults = BootstrapTable.DEFAULTS;
$.fn.bootstrapTable.columnDefaults = BootstrapTable.COLUMN_DEFAULTS;
$.fn.bootstrapTable.events = BootstrapTable.EVENTS;
@@ -11499,7 +11543,7 @@
}
}
function _createClass(e, r, t) {
- return r && _defineProperties(e.prototype, r), Object.defineProperty(e, "prototype", {
+ return _defineProperties(e.prototype, r), Object.defineProperty(e, "prototype", {
writable: !1
}), e;
}
@@ -11551,11 +11595,11 @@
for (; !{}.hasOwnProperty.call(t, o) && null !== (t = _getPrototypeOf(t)););
return t;
}
- function _superPropGet(t, e, r, o) {
- var p = _get(_getPrototypeOf(t.prototype ), e, r);
- return function (t) {
- return p.apply(r, t);
- } ;
+ function _superPropGet(t, o, e, r) {
+ var p = _get(_getPrototypeOf(t.prototype ), o, e);
+ return "function" == typeof p ? function (t) {
+ return p.apply(e, t);
+ } : p;
}
function _toPrimitive(t, r) {
if ("object" != typeof t || !t) return t;
@@ -11969,9 +12013,9 @@
/* eslint-disable es/no-symbol -- required for testing */
var NATIVE_SYMBOL = requireSymbolConstructorDetection();
- useSymbolAsUid = NATIVE_SYMBOL
- && !Symbol.sham
- && typeof Symbol.iterator == 'symbol';
+ useSymbolAsUid = NATIVE_SYMBOL &&
+ !Symbol.sham &&
+ typeof Symbol.iterator == 'symbol';
return useSymbolAsUid;
}
@@ -12122,10 +12166,10 @@
var store = sharedStore.exports = globalThis[SHARED] || defineGlobalProperty(SHARED, {});
(store.versions || (store.versions = [])).push({
- version: '3.38.1',
+ version: '3.39.0',
mode: IS_PURE ? 'pure' : 'global',
copyright: '© 2014-2024 Denis Pushkarev (zloirock.ru)',
- license: 'https://github.com/zloirock/core-js/blob/v3.38.1/LICENSE',
+ license: 'https://github.com/zloirock/core-js/blob/v3.39.0/LICENSE',
source: 'https://github.com/zloirock/core-js'
});
return sharedStore.exports;
@@ -14131,7 +14175,7 @@
}
}
function _createClass(e, r, t) {
- return r && _defineProperties(e.prototype, r), Object.defineProperty(e, "prototype", {
+ return _defineProperties(e.prototype, r), Object.defineProperty(e, "prototype", {
writable: !1
}), e;
}
@@ -14239,21 +14283,21 @@
for (; !{}.hasOwnProperty.call(t, o) && null !== (t = _getPrototypeOf(t)););
return t;
}
- function _superPropGet(t, e, r, o) {
- var p = _get(_getPrototypeOf(1 & o ? t.prototype : t), e, r);
- return 2 & o ? function (t) {
- return p.apply(r, t);
+ function _superPropGet(t, o, e, r) {
+ var p = _get(_getPrototypeOf(1 & r ? t.prototype : t), o, e);
+ return 2 & r && "function" == typeof p ? function (t) {
+ return p.apply(e, t);
} : p;
}
function _toPrimitive(t, r) {
if ("object" != typeof t || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
- var i = e.call(t, r || "default");
+ var i = e.call(t, r);
if ("object" != typeof i) return i;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
- return ("string" === r ? String : Number)(t);
+ return (String )(t);
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, "string");
@@ -14664,9 +14708,9 @@
/* eslint-disable es/no-symbol -- required for testing */
var NATIVE_SYMBOL = requireSymbolConstructorDetection();
- useSymbolAsUid = NATIVE_SYMBOL
- && !Symbol.sham
- && typeof Symbol.iterator == 'symbol';
+ useSymbolAsUid = NATIVE_SYMBOL &&
+ !Symbol.sham &&
+ typeof Symbol.iterator == 'symbol';
return useSymbolAsUid;
}
@@ -14817,10 +14861,10 @@
var store = sharedStore.exports = globalThis[SHARED] || defineGlobalProperty(SHARED, {});
(store.versions || (store.versions = [])).push({
- version: '3.38.1',
+ version: '3.39.0',
mode: IS_PURE ? 'pure' : 'global',
copyright: '© 2014-2024 Denis Pushkarev (zloirock.ru)',
- license: 'https://github.com/zloirock/core-js/blob/v3.38.1/LICENSE',
+ license: 'https://github.com/zloirock/core-js/blob/v3.39.0/LICENSE',
source: 'https://github.com/zloirock/core-js'
});
return sharedStore.exports;
@@ -17509,13 +17553,12 @@
forceExport: false,
forceHide: false
});
- Object.assign($.fn.bootstrapTable.defaults.icons, {
- export: {
- bootstrap3: 'glyphicon-export icon-share',
- bootstrap5: 'bi-download',
- materialize: 'file_download',
- 'bootstrap-table': 'icon-download'
- }[$.fn.bootstrapTable.theme] || 'fa-download'
+ Utils.assignIcons($.fn.bootstrapTable.icons, 'export', {
+ glyphicon: 'glyphicon-export icon-share',
+ fa: 'fa-download',
+ bi: 'bi-download',
+ icon: 'icon-download',
+ 'material-icons': 'file_download'
});
Object.assign($.fn.bootstrapTable.locales, {
formatExport: function formatExport() {
@@ -17797,7 +17840,7 @@
}
}
function _createClass(e, r, t) {
- return r && _defineProperties(e.prototype, r), Object.defineProperty(e, "prototype", {
+ return _defineProperties(e.prototype, r), Object.defineProperty(e, "prototype", {
writable: !1
}), e;
}
@@ -17927,11 +17970,11 @@
for (; !{}.hasOwnProperty.call(t, o) && null !== (t = _getPrototypeOf(t)););
return t;
}
- function _superPropGet(t, e, r, o) {
- var p = _get(_getPrototypeOf(t.prototype ), e, r);
- return function (t) {
- return p.apply(r, t);
- } ;
+ function _superPropGet(t, o, e, r) {
+ var p = _get(_getPrototypeOf(t.prototype ), o, e);
+ return "function" == typeof p ? function (t) {
+ return p.apply(e, t);
+ } : p;
}
function _toPrimitive(t, r) {
if ("object" != typeof t || !t) return t;
@@ -18352,9 +18395,9 @@
/* eslint-disable es/no-symbol -- required for testing */
var NATIVE_SYMBOL = requireSymbolConstructorDetection();
- useSymbolAsUid = NATIVE_SYMBOL
- && !Symbol.sham
- && typeof Symbol.iterator == 'symbol';
+ useSymbolAsUid = NATIVE_SYMBOL &&
+ !Symbol.sham &&
+ typeof Symbol.iterator == 'symbol';
return useSymbolAsUid;
}
@@ -18505,10 +18548,10 @@
var store = sharedStore.exports = globalThis[SHARED] || defineGlobalProperty(SHARED, {});
(store.versions || (store.versions = [])).push({
- version: '3.38.1',
+ version: '3.39.0',
mode: IS_PURE ? 'pure' : 'global',
copyright: '© 2014-2024 Denis Pushkarev (zloirock.ru)',
- license: 'https://github.com/zloirock/core-js/blob/v3.38.1/LICENSE',
+ license: 'https://github.com/zloirock/core-js/blob/v3.39.0/LICENSE',
source: 'https://github.com/zloirock/core-js'
});
return sharedStore.exports;
@@ -21568,6 +21611,7 @@
pageNumber: 'bs.table.pageNumber',
pageList: 'bs.table.pageList',
hiddenColumns: 'bs.table.hiddenColumns',
+ columns: 'bs.table.columns',
cardView: 'bs.table.cardView',
customView: 'bs.table.customView',
searchText: 'bs.table.searchText',
@@ -21585,6 +21629,9 @@
return navigator.cookieEnabled;
},
isCookieEnabled: function isCookieEnabled(that, cookieName) {
+ if (cookieName === 'bs.table.columns') {
+ return that.options.cookiesEnabled.includes('bs.table.hiddenColumns');
+ }
return that.options.cookiesEnabled.includes(cookieName);
},
setCookie: function setCookie(that, cookieName, cookieValue) {
@@ -21741,6 +21788,7 @@
return _createClass(_class, [{
key: "init",
value: function init() {
+ var _this = this;
if (this.options.cookie) {
if (this.options.cookieStorage === 'cookieStorage' && !UtilsCookie.isCookieSupportedByBrowser()) {
throw new Error('Cookies are not enabled in this browser.');
@@ -21756,6 +21804,7 @@
try {
filterByCookie = JSON.parse(filterByCookieValue);
} catch (e) {
+ console.error(e);
throw new Error('Could not parse the json of the filterBy cookie!');
}
this.filterColumns = filterByCookie ? filterByCookie : {};
@@ -21765,24 +21814,23 @@
this._filterControlValuesLoaded = false;
this.options.cookiesEnabled = typeof this.options.cookiesEnabled === 'string' ? this.options.cookiesEnabled.replace('[', '').replace(']', '').replace(/'/g, '').replace(/ /g, '').split(',') : this.options.cookiesEnabled;
if (this.options.filterControl) {
- var that = this;
this.$el.on('column-search.bs.table', function (e, field, text) {
var isNewField = true;
- for (var i = 0; i < that._filterControls.length; i++) {
- if (that._filterControls[i].field === field) {
- that._filterControls[i].text = text;
+ for (var i = 0; i < _this._filterControls.length; i++) {
+ if (_this._filterControls[i].field === field) {
+ _this._filterControls[i].text = text;
isNewField = false;
break;
}
}
if (isNewField) {
- that._filterControls.push({
+ _this._filterControls.push({
field: field,
text: text
});
}
- UtilsCookie.setCookie(that, UtilsCookie.cookieIds.filterControl, JSON.stringify(that._filterControls));
- }).on('created-controls.bs.table', UtilsCookie.initCookieFilters(that));
+ UtilsCookie.setCookie(_this, UtilsCookie.cookieIds.filterControl, JSON.stringify(_this._filterControls));
+ }).on('created-controls.bs.table', UtilsCookie.initCookieFilters(this));
}
}
_superPropGet(_class, "init", this)([]);
@@ -21912,6 +21960,9 @@
UtilsCookie.setCookie(this, UtilsCookie.cookieIds.hiddenColumns, JSON.stringify(this.getHiddenColumns().map(function (column) {
return column.field;
})));
+ UtilsCookie.setCookie(this, UtilsCookie.cookieIds.columns, JSON.stringify(this.columns.map(function (column) {
+ return column.field;
+ })));
}
}, {
key: "_toggleAllColumns",
@@ -21926,6 +21977,9 @@
UtilsCookie.setCookie(this, UtilsCookie.cookieIds.hiddenColumns, JSON.stringify(this.getHiddenColumns().map(function (column) {
return column.field;
})));
+ UtilsCookie.setCookie(this, UtilsCookie.cookieIds.columns, JSON.stringify(this.columns.map(function (column) {
+ return column.field;
+ })));
}
}, {
key: "toggleView",
@@ -22008,15 +22062,20 @@
var cardViewCookie = UtilsCookie.getCookie(this, UtilsCookie.cookieIds.cardView);
var customViewCookie = UtilsCookie.getCookie(this, UtilsCookie.cookieIds.customView);
var hiddenColumnsCookieValue = UtilsCookie.getCookie(this, UtilsCookie.cookieIds.hiddenColumns);
+ var columnsCookieValue = UtilsCookie.getCookie(this, UtilsCookie.cookieIds.columns);
var hiddenColumnsCookie = {};
+ var columnsCookie = {};
try {
hiddenColumnsCookie = JSON.parse(hiddenColumnsCookieValue);
+ columnsCookie = JSON.parse(columnsCookieValue);
} catch (e) {
- throw new Error('Could not parse the json of the hidden columns cookie!', hiddenColumnsCookieValue);
+ console.error(e);
+ throw new Error('Could not parse the json of the columns cookie!');
}
try {
sortPriorityCookie = JSON.parse(sortPriorityCookie);
} catch (e) {
+ console.error(e);
throw new Error('Could not parse the json of the sortPriority cookie!', sortPriorityCookie);
}
if (!sortPriorityCookie) {
@@ -22050,12 +22109,15 @@
}
this.customViewDefaultView = customViewCookie === 'true';
if (hiddenColumnsCookie) {
+ columnsCookie = columnsCookie || this.columns.map(function (column) {
+ return column.field;
+ });
var _iterator2 = _createForOfIteratorHelper(this.columns),
_step2;
try {
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
var column = _step2.value;
- if (!column.switchable) {
+ if (!column.switchable || !columnsCookie.includes(column.field)) {
continue;
}
column.visible = this.isSelectionColumn(column) || !hiddenColumnsCookie.includes(column.field);
@@ -22070,14 +22132,13 @@
}, {
key: "getCookies",
value: function getCookies() {
- var bootstrapTable = this;
var cookies = {};
for (var _i = 0, _Object$entries = Object.entries(UtilsCookie.cookieIds); _i < _Object$entries.length; _i++) {
var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
key = _Object$entries$_i[0],
value = _Object$entries$_i[1];
- cookies[key] = UtilsCookie.getCookie(bootstrapTable, value);
- if (key === 'columns' || key === 'hiddenColumns' || key === 'sortPriority') {
+ cookies[key] = UtilsCookie.getCookie(this, value);
+ if (['columns', 'hiddenColumns', 'sortPriority'].includes(key)) {
cookies[key] = JSON.parse(cookies[key]);
}
}
@@ -22094,12 +22155,12 @@
}, {
key: "configureStorage",
value: function configureStorage() {
- var that = this;
+ var _this2 = this;
this._storage = {};
switch (this.options.cookieStorage) {
case 'cookieStorage':
this._storage.setItem = function (cookieName, cookieValue) {
- document.cookie = [cookieName, '=', encodeURIComponent(cookieValue), "; expires=".concat(UtilsCookie.calculateExpiration(that.options.cookieExpire)), that.options.cookiePath ? "; path=".concat(that.options.cookiePath) : '', that.options.cookieDomain ? "; domain=".concat(that.options.cookieDomain) : '', that.options.cookieSecure ? '; secure' : '', ";SameSite=".concat(that.options.cookieSameSite)].join('');
+ document.cookie = [cookieName, '=', encodeURIComponent(cookieValue), "; expires=".concat(UtilsCookie.calculateExpiration(_this2.options.cookieExpire)), _this2.options.cookiePath ? "; path=".concat(_this2.options.cookiePath) : '', _this2.options.cookieDomain ? "; domain=".concat(_this2.options.cookieDomain) : '', _this2.options.cookieSecure ? '; secure' : '', ";SameSite=".concat(_this2.options.cookieSameSite)].join('');
};
this._storage.getItem = function (cookieName) {
var value = "; ".concat(document.cookie);
@@ -22107,7 +22168,7 @@
return parts.length === 2 ? decodeURIComponent(parts.pop().split(';').shift()) : null;
};
this._storage.removeItem = function (cookieName) {
- document.cookie = [encodeURIComponent(cookieName), '=', '; expires=Thu, 01 Jan 1970 00:00:00 GMT', that.options.cookiePath ? "; path=".concat(that.options.cookiePath) : '', that.options.cookieDomain ? "; domain=".concat(that.options.cookieDomain) : '', ";SameSite=".concat(that.options.cookieSameSite)].join('');
+ document.cookie = [encodeURIComponent(cookieName), '=', '; expires=Thu, 01 Jan 1970 00:00:00 GMT', _this2.options.cookiePath ? "; path=".concat(_this2.options.cookiePath) : '', _this2.options.cookieDomain ? "; domain=".concat(_this2.options.cookieDomain) : '', ";SameSite=".concat(_this2.options.cookieSameSite)].join('');
};
break;
case 'localStorage':
@@ -22137,13 +22198,13 @@
throw new Error('The following options must be set while using the customStorage: cookieCustomStorageSet, cookieCustomStorageGet and cookieCustomStorageDelete');
}
this._storage.setItem = function (cookieName, cookieValue) {
- Utils.calculateObjectValue(that.options, that.options.cookieCustomStorageSet, [cookieName, cookieValue], '');
+ Utils.calculateObjectValue(_this2.options, _this2.options.cookieCustomStorageSet, [cookieName, cookieValue], '');
};
this._storage.getItem = function (cookieName) {
- return Utils.calculateObjectValue(that.options, that.options.cookieCustomStorageGet, [cookieName], '');
+ return Utils.calculateObjectValue(_this2.options, _this2.options.cookieCustomStorageGet, [cookieName], '');
};
this._storage.removeItem = function (cookieName) {
- Utils.calculateObjectValue(that.options, that.options.cookieCustomStorageDelete, [cookieName], '');
+ Utils.calculateObjectValue(_this2.options, _this2.options.cookieCustomStorageDelete, [cookieName], '');
};
break;
default:
@@ -22178,7 +22239,7 @@
}
}
function _createClass(e, r, t) {
- return r && _defineProperties(e.prototype, r), Object.defineProperty(e, "prototype", {
+ return _defineProperties(e.prototype, r), Object.defineProperty(e, "prototype", {
writable: !1
}), e;
}
@@ -22230,11 +22291,11 @@
for (; !{}.hasOwnProperty.call(t, o) && null !== (t = _getPrototypeOf(t)););
return t;
}
- function _superPropGet(t, e, r, o) {
- var p = _get(_getPrototypeOf(t.prototype ), e, r);
- return function (t) {
- return p.apply(r, t);
- } ;
+ function _superPropGet(t, o, e, r) {
+ var p = _get(_getPrototypeOf(t.prototype ), o, e);
+ return "function" == typeof p ? function (t) {
+ return p.apply(e, t);
+ } : p;
}
function _toPrimitive(t, r) {
if ("object" != typeof t || !t) return t;
@@ -22648,9 +22709,9 @@
/* eslint-disable es/no-symbol -- required for testing */
var NATIVE_SYMBOL = requireSymbolConstructorDetection();
- useSymbolAsUid = NATIVE_SYMBOL
- && !Symbol.sham
- && typeof Symbol.iterator == 'symbol';
+ useSymbolAsUid = NATIVE_SYMBOL &&
+ !Symbol.sham &&
+ typeof Symbol.iterator == 'symbol';
return useSymbolAsUid;
}
@@ -22801,10 +22862,10 @@
var store = sharedStore.exports = globalThis[SHARED] || defineGlobalProperty(SHARED, {});
(store.versions || (store.versions = [])).push({
- version: '3.38.1',
+ version: '3.39.0',
mode: IS_PURE ? 'pure' : 'global',
copyright: '© 2014-2024 Denis Pushkarev (zloirock.ru)',
- license: 'https://github.com/zloirock/core-js/blob/v3.38.1/LICENSE',
+ license: 'https://github.com/zloirock/core-js/blob/v3.39.0/LICENSE',
source: 'https://github.com/zloirock/core-js'
});
return sharedStore.exports;
@@ -24630,7 +24691,7 @@
}
}
function _createClass(e, r, t) {
- return r && _defineProperties(e.prototype, r), Object.defineProperty(e, "prototype", {
+ return _defineProperties(e.prototype, r), Object.defineProperty(e, "prototype", {
writable: !1
}), e;
}
@@ -24712,11 +24773,11 @@
for (; !{}.hasOwnProperty.call(t, o) && null !== (t = _getPrototypeOf(t)););
return t;
}
- function _superPropGet(t, e, r, o) {
- var p = _get(_getPrototypeOf(t.prototype ), e, r);
- return function (t) {
- return p.apply(r, t);
- } ;
+ function _superPropGet(t, o, e, r) {
+ var p = _get(_getPrototypeOf(t.prototype ), o, e);
+ return "function" == typeof p ? function (t) {
+ return p.apply(e, t);
+ } : p;
}
function _toPrimitive(t, r) {
if ("object" != typeof t || !t) return t;
@@ -25137,9 +25198,9 @@
/* eslint-disable es/no-symbol -- required for testing */
var NATIVE_SYMBOL = requireSymbolConstructorDetection();
- useSymbolAsUid = NATIVE_SYMBOL
- && !Symbol.sham
- && typeof Symbol.iterator == 'symbol';
+ useSymbolAsUid = NATIVE_SYMBOL &&
+ !Symbol.sham &&
+ typeof Symbol.iterator == 'symbol';
return useSymbolAsUid;
}
@@ -25290,10 +25351,10 @@
var store = sharedStore.exports = globalThis[SHARED] || defineGlobalProperty(SHARED, {});
(store.versions || (store.versions = [])).push({
- version: '3.38.1',
+ version: '3.39.0',
mode: IS_PURE ? 'pure' : 'global',
copyright: '© 2014-2024 Denis Pushkarev (zloirock.ru)',
- license: 'https://github.com/zloirock/core-js/blob/v3.38.1/LICENSE',
+ license: 'https://github.com/zloirock/core-js/blob/v3.39.0/LICENSE',
source: 'https://github.com/zloirock/core-js'
});
return sharedStore.exports;
@@ -29823,7 +29884,7 @@ if(xr(e,"index.xml"))throw new Error("Unsupported NUMBERS 09 file");throw new Er
}
}
function _createClass(e, r, t) {
- return r && _defineProperties(e.prototype, r), Object.defineProperty(e, "prototype", {
+ return _defineProperties(e.prototype, r), Object.defineProperty(e, "prototype", {
writable: !1
}), e;
}
@@ -29875,11 +29936,11 @@ if(xr(e,"index.xml"))throw new Error("Unsupported NUMBERS 09 file");throw new Er
for (; !{}.hasOwnProperty.call(t, o) && null !== (t = _getPrototypeOf(t)););
return t;
}
- function _superPropGet(t, e, r, o) {
- var p = _get(_getPrototypeOf(t.prototype ), e, r);
- return function (t) {
- return p.apply(r, t);
- } ;
+ function _superPropGet(t, o, e, r) {
+ var p = _get(_getPrototypeOf(t.prototype ), o, e);
+ return "function" == typeof p ? function (t) {
+ return p.apply(e, t);
+ } : p;
}
function _toPrimitive(t, r) {
if ("object" != typeof t || !t) return t;
@@ -30293,9 +30354,9 @@ if(xr(e,"index.xml"))throw new Error("Unsupported NUMBERS 09 file");throw new Er
/* eslint-disable es/no-symbol -- required for testing */
var NATIVE_SYMBOL = requireSymbolConstructorDetection();
- useSymbolAsUid = NATIVE_SYMBOL
- && !Symbol.sham
- && typeof Symbol.iterator == 'symbol';
+ useSymbolAsUid = NATIVE_SYMBOL &&
+ !Symbol.sham &&
+ typeof Symbol.iterator == 'symbol';
return useSymbolAsUid;
}
@@ -30446,10 +30507,10 @@ if(xr(e,"index.xml"))throw new Error("Unsupported NUMBERS 09 file");throw new Er
var store = sharedStore.exports = globalThis[SHARED] || defineGlobalProperty(SHARED, {});
(store.versions || (store.versions = [])).push({
- version: '3.38.1',
+ version: '3.39.0',
mode: IS_PURE ? 'pure' : 'global',
copyright: '© 2014-2024 Denis Pushkarev (zloirock.ru)',
- license: 'https://github.com/zloirock/core-js/blob/v3.38.1/LICENSE',
+ license: 'https://github.com/zloirock/core-js/blob/v3.39.0/LICENSE',
source: 'https://github.com/zloirock/core-js'
});
return sharedStore.exports;
@@ -32278,7 +32339,7 @@ if(xr(e,"index.xml"))throw new Error("Unsupported NUMBERS 09 file");throw new Er
}
}
function _createClass(e, r, t) {
- return r && _defineProperties(e.prototype, r), Object.defineProperty(e, "prototype", {
+ return _defineProperties(e.prototype, r), Object.defineProperty(e, "prototype", {
writable: !1
}), e;
}
@@ -32414,11 +32475,11 @@ if(xr(e,"index.xml"))throw new Error("Unsupported NUMBERS 09 file");throw new Er
for (; !{}.hasOwnProperty.call(t, o) && null !== (t = _getPrototypeOf(t)););
return t;
}
- function _superPropGet(t, e, r, o) {
- var p = _get(_getPrototypeOf(t.prototype ), e, r);
- return function (t) {
- return p.apply(r, t);
- } ;
+ function _superPropGet(t, o, e, r) {
+ var p = _get(_getPrototypeOf(t.prototype ), o, e);
+ return "function" == typeof p ? function (t) {
+ return p.apply(e, t);
+ } : p;
}
function _toConsumableArray(r) {
return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread();
@@ -32842,9 +32903,9 @@ if(xr(e,"index.xml"))throw new Error("Unsupported NUMBERS 09 file");throw new Er
/* eslint-disable es/no-symbol -- required for testing */
var NATIVE_SYMBOL = requireSymbolConstructorDetection();
- useSymbolAsUid = NATIVE_SYMBOL
- && !Symbol.sham
- && typeof Symbol.iterator == 'symbol';
+ useSymbolAsUid = NATIVE_SYMBOL &&
+ !Symbol.sham &&
+ typeof Symbol.iterator == 'symbol';
return useSymbolAsUid;
}
@@ -32995,10 +33056,10 @@ if(xr(e,"index.xml"))throw new Error("Unsupported NUMBERS 09 file");throw new Er
var store = sharedStore.exports = globalThis[SHARED] || defineGlobalProperty(SHARED, {});
(store.versions || (store.versions = [])).push({
- version: '3.38.1',
+ version: '3.39.0',
mode: IS_PURE ? 'pure' : 'global',
copyright: '© 2014-2024 Denis Pushkarev (zloirock.ru)',
- license: 'https://github.com/zloirock/core-js/blob/v3.38.1/LICENSE',
+ license: 'https://github.com/zloirock/core-js/blob/v3.39.0/LICENSE',
source: 'https://github.com/zloirock/core-js'
});
return sharedStore.exports;
@@ -35645,27 +35706,18 @@ if(xr(e,"index.xml"))throw new Error("Unsupported NUMBERS 09 file");throw new Er
var Utils = $.fn.bootstrapTable.utils;
var theme = {
bootstrap3: {
- icons: {
- advancedSearchIcon: 'glyphicon-chevron-down'
- },
classes: {},
html: {
modal: "\n
\n "
}
},
bootstrap4: {
- icons: {
- advancedSearchIcon: 'fa-chevron-down'
- },
classes: {},
html: {
modal: "\n
\n "
}
},
bootstrap5: {
- icons: {
- advancedSearchIcon: 'bi-chevron-down'
- },
classes: {
formGroup: 'mb-3'
},
@@ -35674,36 +35726,24 @@ if(xr(e,"index.xml"))throw new Error("Unsupported NUMBERS 09 file");throw new Er
}
},
bulma: {
- icons: {
- advancedSearchIcon: 'fa-chevron-down'
- },
classes: {},
html: {
modal: "\n
\n "
}
},
foundation: {
- icons: {
- advancedSearchIcon: 'fa-chevron-down'
- },
classes: {},
html: {
modal: "\n
\n "
}
},
materialize: {
- icons: {
- advancedSearchIcon: 'expand_more'
- },
classes: {},
html: {
modal: "\n
\n "
}
},
semantic: {
- icons: {
- advancedSearchIcon: 'fa-chevron-down'
- },
classes: {},
html: {
modal: "\n
\n "
@@ -35720,8 +35760,11 @@ if(xr(e,"index.xml"))throw new Error("Unsupported NUMBERS 09 file");throw new Er
return false;
}
});
- Object.assign($.fn.bootstrapTable.defaults.icons, {
- advancedSearchIcon: theme.icons.advancedSearchIcon
+ Utils.assignIcons($.fn.bootstrapTable.icons, 'advancedSearchIcon', {
+ glyphicon: 'glyphicon-chevron-down',
+ fa: 'fa-chevron-down',
+ bi: 'bi-chevron-down',
+ 'material-icons': 'expand_more'
});
Object.assign($.fn.bootstrapTable.events, {
'column-advanced-search.bs.table': 'onColumnAdvancedSearch'
diff --git a/public/mix-manifest.json b/public/mix-manifest.json
index 316037922..295126469 100644
--- a/public/mix-manifest.json
+++ b/public/mix-manifest.json
@@ -1,9 +1,9 @@
{
- "/js/build/app.js": "/js/build/app.js?id=578c0308b8a324cc78c7786da9406022",
+ "/js/build/app.js": "/js/build/app.js?id=607de09b70b83ef82a427e4b36341682",
"/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=06c13e817cc022028b3f4a33c0ca303a",
- "/css/dist/skins/_all-skins.css": "/css/dist/skins/_all-skins.css?id=e71ef4171dee5da63af390966ac60ffc",
- "/css/build/overrides.css": "/css/build/overrides.css?id=6528155ed5ed8fddf4047de7f0d0298d",
- "/css/build/app.css": "/css/build/app.css?id=3422f2ca2056b952c3c361adf00c10b8",
+ "/css/dist/skins/_all-skins.css": "/css/dist/skins/_all-skins.css?id=79aa889a1a6691013be6c342ca7391cd",
+ "/css/build/overrides.css": "/css/build/overrides.css?id=4d62149a0ee9dc139bdf03ff2f83930d",
+ "/css/build/app.css": "/css/build/app.css?id=d47ce0dc14671bb4e462e111001488e5",
"/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=4ea0068716c1bb2434d87a16d51b98c9",
"/css/dist/skins/skin-yellow.css": "/css/dist/skins/skin-yellow.css?id=7b315b9612b8fde8f9c5b0ddb6bba690",
"/css/dist/skins/skin-yellow-dark.css": "/css/dist/skins/skin-yellow-dark.css?id=ea22079836a432d7f46a5d390c445e13",
@@ -16,10 +16,10 @@
"/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=0a82a6ae6bb4e58fe62d162c4fb50397",
"/css/dist/skins/skin-green-dark.css": "/css/dist/skins/skin-green-dark.css?id=bb302302d9566adf783a2b7dc31e840c",
"/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=da6c7997d9de2f8329142399f0ce50da",
- "/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=f677207c6cf9678eb539abecb408c374",
+ "/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=a82b065847bf3cd5d713c04ee8dc86c6",
"/css/dist/skins/skin-blue-dark.css": "/css/dist/skins/skin-blue-dark.css?id=6ea836d8126de101081c49abbdb89417",
"/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=76482123f6c70e866d6b971ba91de7bb",
- "/css/dist/all.css": "/css/dist/all.css?id=c16aa8b273e295ae741b018af6e3e05c",
+ "/css/dist/all.css": "/css/dist/all.css?id=7c861c2086473c513fe26c21e3c4d433",
"/css/dist/signature-pad.css": "/css/dist/signature-pad.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/css/dist/signature-pad.min.css": "/css/dist/signature-pad.min.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/js/select2/i18n/af.js": "/js/select2/i18n/af.js?id=4f6fcd73488ce79fae1b7a90aceaecde",
@@ -90,13 +90,13 @@
"/css/webfonts/fa-solid-900.woff2": "/css/webfonts/fa-solid-900.woff2?id=109ad919b74a62a8a223361da1651bbc",
"/css/webfonts/fa-v4compatibility.ttf": "/css/webfonts/fa-v4compatibility.ttf?id=53c2e50ef821f7b8dd514611d5e0772c",
"/css/webfonts/fa-v4compatibility.woff2": "/css/webfonts/fa-v4compatibility.woff2?id=331c85bd61ffa93af09273d1bc2add5a",
- "/js/dist/bootstrap-table-locale-all.min.js": "/js/dist/bootstrap-table-locale-all.min.js?id=c5445e15be5ce91a9ffef05e08ad6898",
- "/js/dist/bootstrap-table-en-US.min.js": "/js/dist/bootstrap-table-en-US.min.js?id=0f6e85ae692d03a3b11cab445ff263ab",
- "/css/dist/skins/_all-skins.min.css": "/css/dist/skins/_all-skins.min.css?id=e71ef4171dee5da63af390966ac60ffc",
+ "/js/dist/bootstrap-table-locale-all.min.js": "/js/dist/bootstrap-table-locale-all.min.js?id=5e93ef0a1889bed3f92a705dc1e92c9b",
+ "/js/dist/bootstrap-table-en-US.min.js": "/js/dist/bootstrap-table-en-US.min.js?id=c0f21fb7e62d6f0a0153f1cdbf26782a",
+ "/css/dist/skins/_all-skins.min.css": "/css/dist/skins/_all-skins.min.css?id=79aa889a1a6691013be6c342ca7391cd",
"/css/dist/skins/skin-black-dark.min.css": "/css/dist/skins/skin-black-dark.min.css?id=06c13e817cc022028b3f4a33c0ca303a",
"/css/dist/skins/skin-black.min.css": "/css/dist/skins/skin-black.min.css?id=76482123f6c70e866d6b971ba91de7bb",
"/css/dist/skins/skin-blue-dark.min.css": "/css/dist/skins/skin-blue-dark.min.css?id=6ea836d8126de101081c49abbdb89417",
- "/css/dist/skins/skin-blue.min.css": "/css/dist/skins/skin-blue.min.css?id=f677207c6cf9678eb539abecb408c374",
+ "/css/dist/skins/skin-blue.min.css": "/css/dist/skins/skin-blue.min.css?id=a82b065847bf3cd5d713c04ee8dc86c6",
"/css/dist/skins/skin-contrast.min.css": "/css/dist/skins/skin-contrast.min.css?id=da6c7997d9de2f8329142399f0ce50da",
"/css/dist/skins/skin-green-dark.min.css": "/css/dist/skins/skin-green-dark.min.css?id=bb302302d9566adf783a2b7dc31e840c",
"/css/dist/skins/skin-green.min.css": "/css/dist/skins/skin-green.min.css?id=0a82a6ae6bb4e58fe62d162c4fb50397",
@@ -108,8 +108,8 @@
"/css/dist/skins/skin-red.min.css": "/css/dist/skins/skin-red.min.css?id=44bf834f2110504a793dadec132a5898",
"/css/dist/skins/skin-yellow-dark.min.css": "/css/dist/skins/skin-yellow-dark.min.css?id=ea22079836a432d7f46a5d390c445e13",
"/css/dist/skins/skin-yellow.min.css": "/css/dist/skins/skin-yellow.min.css?id=7b315b9612b8fde8f9c5b0ddb6bba690",
- "/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=c384582a6ba08903af353be861ffe74e",
+ "/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=54d676a6ea8677dd48f6c4b3041292cf",
"/js/build/vendor.js": "/js/build/vendor.js?id=89dffa552c6e3abe3a2aac6c9c7b466b",
- "/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=b4c3069f1a292527a96c058b77b28d69",
- "/js/dist/all.js": "/js/dist/all.js?id=eeeac92878ac0b207ad7f39d593f62c3"
+ "/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=757648759dcd365f5708e95b985971ff",
+ "/js/dist/all.js": "/js/dist/all.js?id=cae553daff19b328b3ba51a62f891442"
}
diff --git a/public/vendor/livewire/livewire.esm.js b/public/vendor/livewire/livewire.esm.js
index 2d7ef4791..0a2a03723 100644
--- a/public/vendor/livewire/livewire.esm.js
+++ b/public/vendor/livewire/livewire.esm.js
@@ -1483,7 +1483,7 @@ var require_module_cjs = __commonJS({
deferredMutations = deferredMutations.concat(mutations);
return;
}
- let addedNodes = /* @__PURE__ */ new Set();
+ let addedNodes = [];
let removedNodes = /* @__PURE__ */ new Set();
let addedAttributes = /* @__PURE__ */ new Map();
let removedAttributes = /* @__PURE__ */ new Map();
@@ -1491,8 +1491,24 @@ var require_module_cjs = __commonJS({
if (mutations[i].target._x_ignoreMutationObserver)
continue;
if (mutations[i].type === "childList") {
- mutations[i].addedNodes.forEach((node) => node.nodeType === 1 && addedNodes.add(node));
- mutations[i].removedNodes.forEach((node) => node.nodeType === 1 && removedNodes.add(node));
+ mutations[i].removedNodes.forEach((node) => {
+ if (node.nodeType !== 1)
+ return;
+ if (!node._x_marker)
+ return;
+ removedNodes.add(node);
+ });
+ mutations[i].addedNodes.forEach((node) => {
+ if (node.nodeType !== 1)
+ return;
+ if (removedNodes.has(node)) {
+ removedNodes.delete(node);
+ return;
+ }
+ if (node._x_marker)
+ return;
+ addedNodes.push(node);
+ });
}
if (mutations[i].type === "attributes") {
let el = mutations[i].target;
@@ -1525,29 +1541,15 @@ var require_module_cjs = __commonJS({
onAttributeAddeds.forEach((i) => i(el, attrs));
});
for (let node of removedNodes) {
- if (addedNodes.has(node))
+ if (addedNodes.some((i) => i.contains(node)))
continue;
onElRemoveds.forEach((i) => i(node));
}
- addedNodes.forEach((node) => {
- node._x_ignoreSelf = true;
- node._x_ignore = true;
- });
for (let node of addedNodes) {
- if (removedNodes.has(node))
- continue;
if (!node.isConnected)
continue;
- delete node._x_ignoreSelf;
- delete node._x_ignore;
onElAddeds.forEach((i) => i(node));
- node._x_ignore = true;
- node._x_ignoreSelf = true;
}
- addedNodes.forEach((node) => {
- delete node._x_ignoreSelf;
- delete node._x_ignore;
- });
addedNodes = null;
removedNodes = null;
addedAttributes = null;
@@ -2050,13 +2052,20 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
function interceptInit(callback) {
initInterceptors2.push(callback);
}
+ var markerDispenser = 1;
function initTree(el, walker = walk, intercept = () => {
}) {
+ if (findClosest(el, (i) => i._x_ignore))
+ return;
deferHandlingDirectives(() => {
walker(el, (el2, skip) => {
+ if (el2._x_marker)
+ return;
intercept(el2, skip);
initInterceptors2.forEach((i) => i(el2, skip));
directives(el2, el2.attributes).forEach((handle) => handle());
+ if (!el2._x_ignore)
+ el2._x_marker = markerDispenser++;
el2._x_ignore && skip();
});
});
@@ -2065,6 +2074,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
walker(root, (el) => {
cleanupElement(el);
cleanupAttributes(el);
+ delete el._x_marker;
});
}
function warnAboutMissingPlugins() {
@@ -2853,7 +2863,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
});
return obj;
}
- var Alpine19 = {
+ var Alpine20 = {
get reactive() {
return reactive;
},
@@ -2866,7 +2876,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
get raw() {
return raw;
},
- version: "3.14.3",
+ version: "3.14.8",
flushAndStopDeferringMutations,
dontAutoEvaluateFunctions,
disableEffectScheduling,
@@ -2919,7 +2929,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
data,
bind: bind2
};
- var alpine_default = Alpine19;
+ var alpine_default = Alpine20;
var import_reactivity10 = __toESM2(require_reactivity());
magic("nextTick", () => nextTick);
magic("dispatch", (el) => dispatch3.bind(dispatch3, el));
@@ -3066,7 +3076,6 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
placeInDom(clone2, target, modifiers);
skipDuringClone(() => {
initTree(clone2);
- clone2._x_ignore = true;
})();
});
el._x_teleportPutBack = () => {
@@ -3822,9 +3831,9 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
}
});
-// ../../../../usr/local/lib/node_modules/@alpinejs/collapse/dist/module.cjs.js
+// ../alpine/packages/collapse/dist/module.cjs.js
var require_module_cjs2 = __commonJS({
- "../../../../usr/local/lib/node_modules/@alpinejs/collapse/dist/module.cjs.js"(exports, module) {
+ "../alpine/packages/collapse/dist/module.cjs.js"(exports, module) {
var __defProp2 = Object.defineProperty;
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
var __getOwnPropNames2 = Object.getOwnPropertyNames;
@@ -3848,8 +3857,8 @@ var require_module_cjs2 = __commonJS({
default: () => module_default
});
module.exports = __toCommonJS(module_exports);
- function src_default(Alpine19) {
- Alpine19.directive("collapse", collapse3);
+ function src_default(Alpine20) {
+ Alpine20.directive("collapse", collapse3);
collapse3.inline = (el, { modifiers }) => {
if (!modifiers.includes("min"))
return;
@@ -3869,7 +3878,7 @@ var require_module_cjs2 = __commonJS({
if (!el._x_isShown)
el.style.overflow = "hidden";
let setFunction = (el2, styles) => {
- let revertFunction = Alpine19.setStyles(el2, styles);
+ let revertFunction = Alpine20.setStyles(el2, styles);
return styles.height ? () => {
} : revertFunction;
};
@@ -3892,12 +3901,12 @@ var require_module_cjs2 = __commonJS({
if (current === full) {
current = floor;
}
- Alpine19.transition(el, Alpine19.setStyles, {
+ Alpine20.transition(el, Alpine20.setStyles, {
during: transitionStyles,
start: { height: current + "px" },
end: { height: full + "px" }
}, () => el._x_isShown = true, () => {
- if (el.getBoundingClientRect().height == full) {
+ if (Math.abs(el.getBoundingClientRect().height - full) < 1) {
el.style.overflow = null;
}
});
@@ -3906,7 +3915,7 @@ var require_module_cjs2 = __commonJS({
}, after = () => {
}) {
let full = el.getBoundingClientRect().height;
- Alpine19.transition(el, setFunction, {
+ Alpine20.transition(el, setFunction, {
during: transitionStyles,
start: { height: full + "px" },
end: { height: floor + "px" }
@@ -3943,9 +3952,9 @@ var require_module_cjs2 = __commonJS({
}
});
-// ../../../../usr/local/lib/node_modules/@alpinejs/focus/dist/module.cjs.js
+// ../alpine/packages/focus/dist/module.cjs.js
var require_module_cjs3 = __commonJS({
- "../../../../usr/local/lib/node_modules/@alpinejs/focus/dist/module.cjs.js"(exports, module) {
+ "../alpine/packages/focus/dist/module.cjs.js"(exports, module) {
var __create2 = Object.create;
var __defProp2 = Object.defineProperty;
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
@@ -4742,14 +4751,14 @@ var require_module_cjs3 = __commonJS({
module.exports = __toCommonJS(module_exports);
var import_focus_trap = __toESM2(require_focus_trap());
var import_tabbable = __toESM2(require_dist());
- function src_default(Alpine19) {
+ function src_default(Alpine20) {
let lastFocused;
let currentFocused;
window.addEventListener("focusin", () => {
lastFocused = currentFocused;
currentFocused = document.activeElement;
});
- Alpine19.magic("focus", (el) => {
+ Alpine20.magic("focus", (el) => {
let within = el;
return {
__noscroll: false,
@@ -4853,7 +4862,7 @@ var require_module_cjs3 = __commonJS({
}
};
});
- Alpine19.directive("trap", Alpine19.skipDuringClone((el, { expression, modifiers }, { effect, evaluateLater, cleanup }) => {
+ Alpine20.directive("trap", Alpine20.skipDuringClone((el, { expression, modifiers }, { effect, evaluateLater, cleanup }) => {
let evaluator = evaluateLater(expression);
let oldValue = false;
let options = {
@@ -4945,9 +4954,9 @@ var require_module_cjs3 = __commonJS({
}
});
-// ../../../../usr/local/lib/node_modules/@alpinejs/persist/dist/module.cjs.js
+// ../alpine/packages/persist/dist/module.cjs.js
var require_module_cjs4 = __commonJS({
- "../../../../usr/local/lib/node_modules/@alpinejs/persist/dist/module.cjs.js"(exports, module) {
+ "../alpine/packages/persist/dist/module.cjs.js"(exports, module) {
var __defProp2 = Object.defineProperty;
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
var __getOwnPropNames2 = Object.getOwnPropertyNames;
@@ -4971,7 +4980,7 @@ var require_module_cjs4 = __commonJS({
persist: () => src_default
});
module.exports = __toCommonJS(module_exports);
- function src_default(Alpine19) {
+ function src_default(Alpine20) {
let persist3 = () => {
let alias;
let storage;
@@ -4986,11 +4995,11 @@ var require_module_cjs4 = __commonJS({
setItem: dummy.set.bind(dummy)
};
}
- return Alpine19.interceptor((initialValue, getter, setter, path, key) => {
+ return Alpine20.interceptor((initialValue, getter, setter, path, key) => {
let lookup = alias || `_x_${path}`;
let initial = storageHas(lookup, storage) ? storageGet(lookup, storage) : initialValue;
setter(initial);
- Alpine19.effect(() => {
+ Alpine20.effect(() => {
let value = getter();
storageSet(lookup, value, storage);
setter(value);
@@ -5006,12 +5015,12 @@ var require_module_cjs4 = __commonJS({
};
});
};
- Object.defineProperty(Alpine19, "$persist", { get: () => persist3() });
- Alpine19.magic("persist", persist3);
- Alpine19.persist = (key, { get, set }, storage = localStorage) => {
+ Object.defineProperty(Alpine20, "$persist", { get: () => persist3() });
+ Alpine20.magic("persist", persist3);
+ Alpine20.persist = (key, { get, set }, storage = localStorage) => {
let initial = storageHas(key, storage) ? storageGet(key, storage) : get();
set(initial);
- Alpine19.effect(() => {
+ Alpine20.effect(() => {
let value = get();
storageSet(key, value, storage);
set(value);
@@ -5034,9 +5043,9 @@ var require_module_cjs4 = __commonJS({
}
});
-// ../../../../usr/local/lib/node_modules/@alpinejs/intersect/dist/module.cjs.js
+// ../alpine/packages/intersect/dist/module.cjs.js
var require_module_cjs5 = __commonJS({
- "../../../../usr/local/lib/node_modules/@alpinejs/intersect/dist/module.cjs.js"(exports, module) {
+ "../alpine/packages/intersect/dist/module.cjs.js"(exports, module) {
var __defProp2 = Object.defineProperty;
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
var __getOwnPropNames2 = Object.getOwnPropertyNames;
@@ -5060,8 +5069,8 @@ var require_module_cjs5 = __commonJS({
intersect: () => src_default
});
module.exports = __toCommonJS(module_exports);
- function src_default(Alpine19) {
- Alpine19.directive("intersect", Alpine19.skipDuringClone((el, { value, expression, modifiers }, { evaluateLater, cleanup }) => {
+ function src_default(Alpine20) {
+ Alpine20.directive("intersect", Alpine20.skipDuringClone((el, { value, expression, modifiers }, { evaluateLater, cleanup }) => {
let evaluate = evaluateLater(expression);
let options = {
rootMargin: getRootMargin(modifiers),
@@ -5142,8 +5151,8 @@ var require_module_cjs6 = __commonJS({
resize: () => src_default
});
module.exports = __toCommonJS(module_exports);
- function src_default(Alpine19) {
- Alpine19.directive("resize", Alpine19.skipDuringClone((el, { value, expression, modifiers }, { evaluateLater, cleanup }) => {
+ function src_default(Alpine20) {
+ Alpine20.directive("resize", Alpine20.skipDuringClone((el, { value, expression, modifiers }, { evaluateLater, cleanup }) => {
let evaluator = evaluateLater(expression);
let evaluate = (width, height) => {
evaluator(() => {
@@ -6387,20 +6396,20 @@ var require_module_cjs7 = __commonJS({
platform: platformWithCache
});
};
- function src_default(Alpine19) {
- Alpine19.magic("anchor", (el) => {
+ function src_default(Alpine20) {
+ Alpine20.magic("anchor", (el) => {
if (!el._x_anchor)
throw "Alpine: No x-anchor directive found on element using $anchor...";
return el._x_anchor;
});
- Alpine19.interceptClone((from, to) => {
+ Alpine20.interceptClone((from, to) => {
if (from && from._x_anchor && !to._x_anchor) {
to._x_anchor = from._x_anchor;
}
});
- Alpine19.directive("anchor", Alpine19.skipDuringClone((el, { expression, modifiers, value }, { cleanup, evaluate: evaluate2 }) => {
+ Alpine20.directive("anchor", Alpine20.skipDuringClone((el, { expression, modifiers, value }, { cleanup, evaluate: evaluate2 }) => {
let { placement, offsetValue, unstyled } = getOptions(modifiers);
- el._x_anchor = Alpine19.reactive({ x: 0, y: 0 });
+ el._x_anchor = Alpine20.reactive({ x: 0, y: 0 });
let reference = evaluate2(expression);
if (!reference)
throw "Alpine: no element provided to x-anchor...";
@@ -6852,6 +6861,7 @@ var require_module_cjs8 = __commonJS({
let holdover = fromKeyHoldovers[toKey];
from2.appendChild(holdover);
currentFrom = holdover;
+ fromKey = getKey(currentFrom);
} else {
if (!shouldSkip(adding, currentTo)) {
let clone = currentTo.cloneNode(true);
@@ -6925,6 +6935,7 @@ var require_module_cjs8 = __commonJS({
if (fromKeys[toKey]) {
currentFrom.replaceWith(fromKeys[toKey]);
currentFrom = fromKeys[toKey];
+ fromKey = getKey(currentFrom);
}
}
if (toKey && fromKey) {
@@ -6933,6 +6944,7 @@ var require_module_cjs8 = __commonJS({
fromKeyHoldovers[fromKey] = currentFrom;
currentFrom.replaceWith(fromKeyNode);
currentFrom = fromKeyNode;
+ fromKey = getKey(currentFrom);
} else {
fromKeyHoldovers[fromKey] = currentFrom;
currentFrom = addNodeBefore(from2, currentTo, currentFrom);
@@ -7078,19 +7090,21 @@ var require_module_cjs8 = __commonJS({
let fromId = from && from._x_bindings && from._x_bindings.id;
if (!fromId)
return;
+ if (!to.setAttribute)
+ return;
to.setAttribute("id", fromId);
to.id = fromId;
}
- function src_default(Alpine19) {
- Alpine19.morph = morph3;
+ function src_default(Alpine20) {
+ Alpine20.morph = morph3;
}
var module_default = src_default;
}
});
-// ../../../../usr/local/lib/node_modules/@alpinejs/mask/dist/module.cjs.js
+// ../alpine/packages/mask/dist/module.cjs.js
var require_module_cjs9 = __commonJS({
- "../../../../usr/local/lib/node_modules/@alpinejs/mask/dist/module.cjs.js"(exports, module) {
+ "../alpine/packages/mask/dist/module.cjs.js"(exports, module) {
var __defProp2 = Object.defineProperty;
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
var __getOwnPropNames2 = Object.getOwnPropertyNames;
@@ -7115,8 +7129,8 @@ var require_module_cjs9 = __commonJS({
stripDown: () => stripDown
});
module.exports = __toCommonJS(module_exports);
- function src_default(Alpine19) {
- Alpine19.directive("mask", (el, { value, expression }, { effect, evaluateLater, cleanup }) => {
+ function src_default(Alpine20) {
+ Alpine20.directive("mask", (el, { value, expression }, { effect, evaluateLater, cleanup }) => {
let templateFn = () => expression;
let lastInputValue = "";
queueMicrotask(() => {
@@ -7125,7 +7139,7 @@ var require_module_cjs9 = __commonJS({
effect(() => {
templateFn = (input) => {
let result;
- Alpine19.dontAutoEvaluateFunctions(() => {
+ Alpine20.dontAutoEvaluateFunctions(() => {
evaluator((value2) => {
result = typeof value2 === "function" ? value2(input) : value2;
}, { scope: {
@@ -7140,8 +7154,13 @@ var require_module_cjs9 = __commonJS({
} else {
processInputValue(el, false);
}
- if (el._x_model)
+ if (el._x_model) {
+ if (el._x_model.get() === el.value)
+ return;
+ if (el._x_model.get() === null && el.value === "")
+ return;
el._x_model.set(el.value);
+ }
});
const controller = new AbortController();
cleanup(() => {
@@ -7356,9 +7375,7 @@ function dataGet(object, key) {
if (key === "")
return object;
return key.split(".").reduce((carry, i) => {
- if (carry === void 0)
- return void 0;
- return carry[i];
+ return carry?.[i];
}, object);
}
function dataSet(object, key, value) {
@@ -7485,6 +7502,9 @@ function handleFileUpload(el, property, component, cleanup) {
if (value === null || value === "") {
el.value = "";
}
+ if (el.multiple && Array.isArray(value) && value.length === 0) {
+ el.value = "";
+ }
});
let clearFileInputValue = () => {
el.value = null;
@@ -8171,6 +8191,7 @@ var aliases = {
"get": "$get",
"set": "$set",
"call": "$call",
+ "hook": "$hook",
"commit": "$commit",
"watch": "$watch",
"entangle": "$entangle",
@@ -8265,6 +8286,16 @@ wireProperty("$watch", (component) => (path, callback) => {
wireProperty("$refresh", (component) => component.$wire.$commit);
wireProperty("$commit", (component) => async () => await requestCommit(component));
wireProperty("$on", (component) => (...params) => listen2(component, ...params));
+wireProperty("$hook", (component) => (name, callback) => {
+ let unhook = on(name, ({ component: hookComponent, ...params }) => {
+ if (hookComponent === void 0)
+ return callback(params);
+ if (hookComponent.id === component.id)
+ return callback({ component: hookComponent, ...params });
+ });
+ component.addCleanup(unhook);
+ return unhook;
+});
wireProperty("$dispatch", (component) => (...params) => dispatch2(component, ...params));
wireProperty("$dispatchSelf", (component) => (...params) => dispatchSelf(component, ...params));
wireProperty("$dispatchTo", () => (...params) => dispatchTo(...params));
@@ -8524,6 +8555,16 @@ function directive(name, callback) {
}
});
}
+function globalDirective(name, callback) {
+ if (customDirectiveNames.has(name))
+ return;
+ customDirectiveNames.add(name);
+ on("directive.global.init", ({ el, directive: directive2, cleanup }) => {
+ if (directive2.value === name) {
+ callback({ el, directive: directive2, cleanup });
+ }
+ });
+}
function getDirectives(el) {
return new DirectiveManager(el);
}
@@ -9019,6 +9060,8 @@ function injectStyles() {
// js/plugins/navigate/popover.js
function packUpPersistedPopovers(persistedEl) {
+ if (!isPopoverSupported())
+ return;
persistedEl.querySelectorAll(":popover-open").forEach((el) => {
el.setAttribute("data-navigate-popover-open", "");
let animations = el.getAnimations();
@@ -9037,6 +9080,8 @@ function packUpPersistedPopovers(persistedEl) {
});
}
function unPackPersistedPopovers(persistedEl) {
+ if (!isPopoverSupported())
+ return;
persistedEl.querySelectorAll("[data-navigate-popover-open]").forEach((el) => {
el.removeAttribute("data-navigate-popover-open");
queueMicrotask(() => {
@@ -9054,6 +9099,9 @@ function unPackPersistedPopovers(persistedEl) {
});
});
}
+function isPopoverSupported() {
+ return typeof document.createElement("div").showPopover === "function";
+}
// js/plugins/navigate/page.js
var oldBodyScriptTagHashes = [];
@@ -9122,6 +9170,8 @@ function mergeNewHead(newHead) {
child.remove();
}
for (let child of Array.from(newHead.children)) {
+ if (child.tagName.toLowerCase() === "noscript")
+ continue;
document.head.appendChild(child);
}
return Promise.all(remoteScriptsPromises);
@@ -9190,8 +9240,8 @@ var enablePersist = true;
var showProgressBar = true;
var restoreScroll = true;
var autofocus = false;
-function navigate_default(Alpine19) {
- Alpine19.navigate = (url) => {
+function navigate_default(Alpine20) {
+ Alpine20.navigate = (url) => {
let destination = createUrlObjectFromString(url);
let prevented = fireEventForOtherLibrariesToHookInto("alpine:navigate", {
url: destination,
@@ -9202,11 +9252,11 @@ function navigate_default(Alpine19) {
return;
navigateTo(destination);
};
- Alpine19.navigate.disableProgressBar = () => {
+ Alpine20.navigate.disableProgressBar = () => {
showProgressBar = false;
};
- Alpine19.addInitSelector(() => `[${Alpine19.prefixed("navigate")}]`);
- Alpine19.directive("navigate", (el, { modifiers }) => {
+ Alpine20.addInitSelector(() => `[${Alpine20.prefixed("navigate")}]`);
+ Alpine20.directive("navigate", (el, { modifiers }) => {
let shouldPrefetchOnHover = modifiers.includes("hover");
shouldPrefetchOnHover && whenThisLinkIsHoveredFor(el, 60, () => {
let destination = extractDestinationFromLink(el);
@@ -9243,7 +9293,7 @@ function navigate_default(Alpine19) {
showProgressBar && finishAndHideProgressBar();
cleanupAlpineElementsOnThePageThatArentInsideAPersistedElement();
updateCurrentPageHtmlInHistoryStateForLaterBackButtonClicks();
- preventAlpineFromPickingUpDomChanges(Alpine19, (andAfterAllThis) => {
+ preventAlpineFromPickingUpDomChanges(Alpine20, (andAfterAllThis) => {
enablePersist && storePersistantElementsForLater((persistedEl) => {
packUpPersistedTeleports(persistedEl);
packUpPersistedPopovers(persistedEl);
@@ -9265,7 +9315,7 @@ function navigate_default(Alpine19) {
setTimeout(() => {
autofocus && autofocusElementsWithTheAutofocusAttribute();
});
- nowInitializeAlpineOnTheNewPage(Alpine19);
+ nowInitializeAlpineOnTheNewPage(Alpine20);
fireEventForOtherLibrariesToHookInto("alpine:navigated");
});
});
@@ -9298,7 +9348,7 @@ function navigate_default(Alpine19) {
storeScrollInformationInHtmlBeforeNavigatingAway();
fireEventForOtherLibrariesToHookInto("alpine:navigating");
updateCurrentPageHtmlInSnapshotCacheForLaterBackButtonClicks(currentPageUrl, currentPageKey);
- preventAlpineFromPickingUpDomChanges(Alpine19, (andAfterAllThis) => {
+ preventAlpineFromPickingUpDomChanges(Alpine20, (andAfterAllThis) => {
enablePersist && storePersistantElementsForLater((persistedEl) => {
packUpPersistedTeleports(persistedEl);
packUpPersistedPopovers(persistedEl);
@@ -9313,7 +9363,7 @@ function navigate_default(Alpine19) {
restoreScrollPositionOrScrollToTop();
andAfterAllThis(() => {
autofocus && autofocusElementsWithTheAutofocusAttribute();
- nowInitializeAlpineOnTheNewPage(Alpine19);
+ nowInitializeAlpineOnTheNewPage(Alpine20);
fireEventForOtherLibrariesToHookInto("alpine:navigated");
});
});
@@ -9328,10 +9378,10 @@ function fetchHtmlOrUsePrefetchedHtml(fromDestination, callback) {
fetchHtml(fromDestination, callback);
});
}
-function preventAlpineFromPickingUpDomChanges(Alpine19, callback) {
- Alpine19.stopObservingMutations();
+function preventAlpineFromPickingUpDomChanges(Alpine20, callback) {
+ Alpine20.stopObservingMutations();
callback((afterAllThis) => {
- Alpine19.startObservingMutations();
+ Alpine20.startObservingMutations();
queueMicrotask(() => {
afterAllThis();
});
@@ -9346,8 +9396,8 @@ function fireEventForOtherLibrariesToHookInto(name, detail) {
document.dispatchEvent(event);
return event.defaultPrevented;
}
-function nowInitializeAlpineOnTheNewPage(Alpine19) {
- Alpine19.initTree(document.body, void 0, (el, skip) => {
+function nowInitializeAlpineOnTheNewPage(Alpine20) {
+ Alpine20.initTree(document.body, void 0, (el, skip) => {
if (el._x_wasPersisted)
skip();
});
@@ -9370,8 +9420,8 @@ function cleanupAlpineElementsOnThePageThatArentInsideAPersistedElement() {
}
// js/plugins/history/index.js
-function history2(Alpine19) {
- Alpine19.magic("queryString", (el, { interceptor }) => {
+function history2(Alpine20) {
+ Alpine20.magic("queryString", (el, { interceptor }) => {
let alias;
let alwaysShow = false;
let usePush = false;
@@ -9380,9 +9430,9 @@ function history2(Alpine19) {
let { initial, replace: replace2, push: push2, pop } = track(queryKey, initialSeedValue, alwaysShow);
setter(initial);
if (!usePush) {
- Alpine19.effect(() => replace2(getter()));
+ Alpine20.effect(() => replace2(getter()));
} else {
- Alpine19.effect(() => push2(getter()));
+ Alpine20.effect(() => push2(getter()));
pop(async (newValue) => {
setter(newValue);
let tillTheEndOfTheMicrotaskQueue = () => Promise.resolve();
@@ -9405,7 +9455,7 @@ function history2(Alpine19) {
};
});
});
- Alpine19.history = { track };
+ Alpine20.history = { track };
}
function track(name, initialSeedValue, alwaysShow = false, except = null) {
let { has, get, set, remove } = queryStringUtils();
@@ -9489,24 +9539,24 @@ function queryStringUtils() {
let search = url.search;
if (!search)
return false;
- let data = fromQueryString(search);
+ let data = fromQueryString(search, key);
return Object.keys(data).includes(key);
},
get(url, key) {
let search = url.search;
if (!search)
return false;
- let data = fromQueryString(search);
+ let data = fromQueryString(search, key);
return data[key];
},
set(url, key, value) {
- let data = fromQueryString(url.search);
+ let data = fromQueryString(url.search, key);
data[key] = stripNulls(unwrap(value));
url.search = toQueryString(data);
return url;
},
remove(url, key) {
- let data = fromQueryString(url.search);
+ let data = fromQueryString(url.search, key);
delete data[key];
url.search = toQueryString(data);
return url;
@@ -9542,7 +9592,7 @@ function toQueryString(data) {
let entries = buildQueryStringEntries(data);
return Object.entries(entries).map(([key, value]) => `${key}=${value}`).join("&");
}
-function fromQueryString(search) {
+function fromQueryString(search, queryKey) {
search = search.replace("?", "");
if (search === "")
return {};
@@ -9561,10 +9611,12 @@ function fromQueryString(search) {
if (typeof value == "undefined")
return;
value = decodeURIComponent(value.replaceAll("+", "%20"));
- if (!key.includes("[")) {
+ let decodedKey = decodeURIComponent(key);
+ let shouldBeHandledAsArray = decodedKey.includes("[") && decodedKey.startsWith(queryKey);
+ if (!shouldBeHandledAsArray) {
data[key] = value;
} else {
- let dotNotatedKey = key.replaceAll("[", ".").replaceAll("]", "");
+ let dotNotatedKey = decodedKey.replaceAll("[", ".").replaceAll("]", "");
insertDotNotatedValueIntoData(dotNotatedKey, value, data);
}
});
@@ -9614,10 +9666,15 @@ function start() {
destroyComponent(component2.id);
});
}
+ let directives = Array.from(el.getAttributeNames()).filter((name) => matchesForLivewireDirective(name)).map((name) => extractDirective(el, name));
+ directives.forEach((directive2) => {
+ trigger("directive.global.init", { el, directive: directive2, cleanup: (callback) => {
+ import_alpinejs5.default.onAttributeRemoved(el, directive2.raw, callback);
+ } });
+ });
let component = closestComponent(el, false);
if (component) {
trigger("element.init", { el, component });
- let directives = Array.from(el.getAttributeNames()).filter((name) => matchesForLivewireDirective(name)).map((name) => extractDirective(el, name));
directives.forEach((directive2) => {
trigger("directive.init", { el, component, directive: directive2, cleanup: (callback) => {
import_alpinejs5.default.onAttributeRemoved(el, directive2.raw, callback);
@@ -9640,7 +9697,7 @@ function ensureLivewireScriptIsntMisplaced() {
}
// js/index.js
-var import_alpinejs17 = __toESM(require_module_cjs());
+var import_alpinejs18 = __toESM(require_module_cjs());
// js/features/supportListeners.js
on("effect", ({ component, effects }) => {
@@ -10366,6 +10423,54 @@ directive("confirm", ({ el, directive: directive2 }) => {
};
});
+// js/directives/wire-current.js
+var import_alpinejs14 = __toESM(require_module_cjs());
+import_alpinejs14.default.addInitSelector(() => `[wire\\:current]`);
+var onPageChanges = /* @__PURE__ */ new Map();
+document.addEventListener("livewire:navigated", () => {
+ onPageChanges.forEach((i) => i(new URL(window.location.href)));
+});
+globalDirective("current", ({ el, directive: directive2, cleanup }) => {
+ let expression = directive2.expression;
+ let options = {
+ exact: directive2.modifiers.includes("exact"),
+ strict: directive2.modifiers.includes("strict")
+ };
+ if (expression.startsWith("#"))
+ return;
+ if (!el.hasAttribute("href"))
+ return;
+ let href = el.getAttribute("href");
+ let hrefUrl = new URL(href, window.location.href);
+ let classes = expression.split(" ").filter(String);
+ let refreshCurrent = (url) => {
+ if (pathMatches(hrefUrl, url, options)) {
+ el.classList.add(...classes);
+ } else {
+ el.classList.remove(...classes);
+ }
+ };
+ refreshCurrent(new URL(window.location.href));
+ onPageChanges.set(el, refreshCurrent);
+ cleanup(() => onPageChanges.delete(el));
+});
+function pathMatches(hrefUrl, actualUrl, options) {
+ if (hrefUrl.hostname !== actualUrl.hostname)
+ return false;
+ let hrefPath = options.strict ? hrefUrl.pathname : hrefUrl.pathname.replace(/\/+$/, "");
+ let actualPath = options.strict ? actualUrl.pathname : actualUrl.pathname.replace(/\/+$/, "");
+ if (options.exact) {
+ return hrefPath === actualPath;
+ }
+ let hrefPathSegments = hrefPath.split("/");
+ let actualPathSegments = actualPath.split("/");
+ for (let i = 0; i < hrefPathSegments.length; i++) {
+ if (hrefPathSegments[i] !== actualPathSegments[i])
+ return false;
+ }
+ return true;
+}
+
// js/directives/shared.js
function toggleBooleanStateDirective(el, directive2, isTruthy, cachedDisplay = null) {
isTruthy = directive2.modifiers.includes("remove") ? !isTruthy : isTruthy;
@@ -10637,8 +10742,8 @@ directive("ignore", ({ el, directive: directive2 }) => {
// js/directives/wire-dirty.js
var refreshDirtyStatesByComponent = new WeakBag();
-on("commit", ({ component, respond }) => {
- respond(() => {
+on("commit", ({ component, succeed }) => {
+ succeed(() => {
setTimeout(() => {
refreshDirtyStatesByComponent.each(component, (i) => i(false));
});
@@ -10685,7 +10790,7 @@ function dirtyTargets(el) {
}
// js/directives/wire-model.js
-var import_alpinejs14 = __toESM(require_module_cjs());
+var import_alpinejs15 = __toESM(require_module_cjs());
directive("model", ({ el, directive: directive2, component, cleanup }) => {
let { expression, modifiers } = directive2;
if (!expression) {
@@ -10703,7 +10808,7 @@ directive("model", ({ el, directive: directive2, component, cleanup }) => {
let isDebounced = modifiers.includes("debounce");
let update = expression.startsWith("$parent") ? () => component.$wire.$parent.$commit() : () => component.$wire.$commit();
let debouncedUpdate = isTextInput(el) && !isDebounced && isLive ? debounce(update, 150) : update;
- import_alpinejs14.default.bind(el, {
+ import_alpinejs15.default.bind(el, {
["@change"]() {
isLazy && update();
},
@@ -10759,14 +10864,14 @@ function debounce(func, wait) {
}
// js/directives/wire-init.js
-var import_alpinejs15 = __toESM(require_module_cjs());
+var import_alpinejs16 = __toESM(require_module_cjs());
directive("init", ({ el, directive: directive2 }) => {
let fullMethod = directive2.expression ?? "$refresh";
- import_alpinejs15.default.evaluate(el, `$wire.${fullMethod}`);
+ import_alpinejs16.default.evaluate(el, `$wire.${fullMethod}`);
});
// js/directives/wire-poll.js
-var import_alpinejs16 = __toESM(require_module_cjs());
+var import_alpinejs17 = __toESM(require_module_cjs());
directive("poll", ({ el, directive: directive2 }) => {
let interval = extractDurationFrom(directive2.modifiers, 2e3);
let { start: start2, pauseWhile, throttleWhile, stopWhen } = poll(() => {
@@ -10780,7 +10885,7 @@ directive("poll", ({ el, directive: directive2 }) => {
stopWhen(() => theElementIsDisconnected(el));
});
function triggerComponentRequest(el, directive2) {
- import_alpinejs16.default.evaluate(el, directive2.expression ? "$wire." + directive2.expression : "$wire.$commit()");
+ import_alpinejs17.default.evaluate(el, directive2.expression ? "$wire." + directive2.expression : "$wire.$commit()");
}
function poll(callback, interval = 2e3) {
let pauseConditions = [];
@@ -10883,7 +10988,7 @@ var Livewire2 = {
dispatch: dispatchGlobal,
on: on2,
get navigate() {
- return import_alpinejs17.default.navigate;
+ return import_alpinejs18.default.navigate;
}
};
var warnAboutMultipleInstancesOf = (entity) => console.warn(`Detected multiple instances of ${entity} running`);
@@ -10892,7 +10997,7 @@ if (window.Livewire)
if (window.Alpine)
warnAboutMultipleInstancesOf("Alpine");
window.Livewire = Livewire2;
-window.Alpine = import_alpinejs17.default;
+window.Alpine = import_alpinejs18.default;
if (window.livewireScriptConfig === void 0) {
window.Alpine.__fromLivewire = true;
document.addEventListener("DOMContentLoaded", () => {
@@ -10902,7 +11007,7 @@ if (window.livewireScriptConfig === void 0) {
Livewire2.start();
});
}
-var export_Alpine = import_alpinejs17.default;
+var export_Alpine = import_alpinejs18.default;
export {
export_Alpine as Alpine,
Livewire2 as Livewire
diff --git a/public/vendor/livewire/livewire.esm.js.map b/public/vendor/livewire/livewire.esm.js.map
index c4af6b08a..199f512a5 100644
--- a/public/vendor/livewire/livewire.esm.js.map
+++ b/public/vendor/livewire/livewire.esm.js.map
@@ -1,7 +1,7 @@
{
"version": 3,
- "sources": ["../../alpine/packages/alpinejs/dist/module.cjs.js", "../../../../../usr/local/lib/node_modules/@alpinejs/collapse/dist/module.cjs.js", "../../../../../usr/local/lib/node_modules/@alpinejs/focus/dist/module.cjs.js", "../../../../../usr/local/lib/node_modules/@alpinejs/persist/dist/module.cjs.js", "../../../../../usr/local/lib/node_modules/@alpinejs/intersect/dist/module.cjs.js", "../node_modules/@alpinejs/resize/dist/module.cjs.js", "../../alpine/packages/anchor/dist/module.cjs.js", "../node_modules/nprogress/nprogress.js", "../../alpine/packages/morph/dist/module.cjs.js", "../../../../../usr/local/lib/node_modules/@alpinejs/mask/dist/module.cjs.js", "../js/utils.js", "../js/features/supportFileUploads.js", "../js/features/supportEntangle.js", "../js/hooks.js", "../js/request/modal.js", "../js/request/pool.js", "../js/request/commit.js", "../js/request/bus.js", "../js/request/index.js", "../js/$wire.js", "../js/component.js", "../js/store.js", "../js/events.js", "../js/directives.js", "../js/lifecycle.js", "../js/plugins/navigate/history.js", "../js/plugins/navigate/links.js", "../js/plugins/navigate/fetch.js", "../js/plugins/navigate/prefetch.js", "../js/plugins/navigate/teleport.js", "../js/plugins/navigate/scroll.js", "../js/plugins/navigate/persist.js", "../js/plugins/navigate/bar.js", "../js/plugins/navigate/popover.js", "../js/plugins/navigate/page.js", "../js/plugins/navigate/index.js", "../js/plugins/history/index.js", "../js/index.js", "../js/features/supportListeners.js", "../js/features/supportScriptsAndAssets.js", "../js/features/supportJsEvaluation.js", "../js/morph.js", "../js/features/supportMorphDom.js", "../js/features/supportDispatches.js", "../js/features/supportDisablingFormsDuringRequest.js", "../js/features/supportPropsAndModelables.js", "../js/features/supportFileDownloads.js", "../js/features/supportLazyLoading.js", "../js/features/supportQueryString.js", "../js/features/supportLaravelEcho.js", "../js/features/supportIsolating.js", "../js/features/supportNavigate.js", "../js/features/supportRedirects.js", "../js/directives/wire-transition.js", "../js/debounce.js", "../js/directives/wire-wildcard.js", "../js/directives/wire-navigate.js", "../js/directives/wire-confirm.js", "../js/directives/shared.js", "../js/directives/wire-offline.js", "../js/directives/wire-loading.js", "../js/directives/wire-stream.js", "../js/directives/wire-replace.js", "../js/directives/wire-ignore.js", "../js/directives/wire-dirty.js", "../js/directives/wire-model.js", "../js/directives/wire-init.js", "../js/directives/wire-poll.js"],
- "sourcesContent": ["var __create = Object.create;\nvar __defProp = Object.defineProperty;\nvar __getOwnPropDesc = Object.getOwnPropertyDescriptor;\nvar __getOwnPropNames = Object.getOwnPropertyNames;\nvar __getProtoOf = Object.getPrototypeOf;\nvar __hasOwnProp = Object.prototype.hasOwnProperty;\nvar __commonJS = (cb, mod) => function __require() {\n return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;\n};\nvar __export = (target, all) => {\n for (var name in all)\n __defProp(target, name, { get: all[name], enumerable: true });\n};\nvar __copyProps = (to, from, except, desc) => {\n if (from && typeof from === \"object\" || typeof from === \"function\") {\n for (let key of __getOwnPropNames(from))\n if (!__hasOwnProp.call(to, key) && key !== except)\n __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });\n }\n return to;\n};\nvar __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(\n // If the importer is in node compatibility mode or this is not an ESM\n // file that has been converted to a CommonJS file using a Babel-\n // compatible transform (i.e. \"__esModule\" has not been set), then set\n // \"default\" to the CommonJS \"module.exports\" for node compatibility.\n isNodeMode || !mod || !mod.__esModule ? __defProp(target, \"default\", { value: mod, enumerable: true }) : target,\n mod\n));\nvar __toCommonJS = (mod) => __copyProps(__defProp({}, \"__esModule\", { value: true }), mod);\n\n// node_modules/@vue/shared/dist/shared.cjs.js\nvar require_shared_cjs = __commonJS({\n \"node_modules/@vue/shared/dist/shared.cjs.js\"(exports) {\n \"use strict\";\n Object.defineProperty(exports, \"__esModule\", { value: true });\n function makeMap(str, expectsLowerCase) {\n const map = /* @__PURE__ */ Object.create(null);\n const list = str.split(\",\");\n for (let i = 0; i < list.length; i++) {\n map[list[i]] = true;\n }\n return expectsLowerCase ? (val) => !!map[val.toLowerCase()] : (val) => !!map[val];\n }\n var PatchFlagNames = {\n [\n 1\n /* TEXT */\n ]: `TEXT`,\n [\n 2\n /* CLASS */\n ]: `CLASS`,\n [\n 4\n /* STYLE */\n ]: `STYLE`,\n [\n 8\n /* PROPS */\n ]: `PROPS`,\n [\n 16\n /* FULL_PROPS */\n ]: `FULL_PROPS`,\n [\n 32\n /* HYDRATE_EVENTS */\n ]: `HYDRATE_EVENTS`,\n [\n 64\n /* STABLE_FRAGMENT */\n ]: `STABLE_FRAGMENT`,\n [\n 128\n /* KEYED_FRAGMENT */\n ]: `KEYED_FRAGMENT`,\n [\n 256\n /* UNKEYED_FRAGMENT */\n ]: `UNKEYED_FRAGMENT`,\n [\n 512\n /* NEED_PATCH */\n ]: `NEED_PATCH`,\n [\n 1024\n /* DYNAMIC_SLOTS */\n ]: `DYNAMIC_SLOTS`,\n [\n 2048\n /* DEV_ROOT_FRAGMENT */\n ]: `DEV_ROOT_FRAGMENT`,\n [\n -1\n /* HOISTED */\n ]: `HOISTED`,\n [\n -2\n /* BAIL */\n ]: `BAIL`\n };\n var slotFlagsText = {\n [\n 1\n /* STABLE */\n ]: \"STABLE\",\n [\n 2\n /* DYNAMIC */\n ]: \"DYNAMIC\",\n [\n 3\n /* FORWARDED */\n ]: \"FORWARDED\"\n };\n var GLOBALS_WHITE_LISTED = \"Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt\";\n var isGloballyWhitelisted = /* @__PURE__ */ makeMap(GLOBALS_WHITE_LISTED);\n var range = 2;\n function generateCodeFrame(source, start2 = 0, end = source.length) {\n let lines = source.split(/(\\r?\\n)/);\n const newlineSequences = lines.filter((_, idx) => idx % 2 === 1);\n lines = lines.filter((_, idx) => idx % 2 === 0);\n let count = 0;\n const res = [];\n for (let i = 0; i < lines.length; i++) {\n count += lines[i].length + (newlineSequences[i] && newlineSequences[i].length || 0);\n if (count >= start2) {\n for (let j = i - range; j <= i + range || end > count; j++) {\n if (j < 0 || j >= lines.length)\n continue;\n const line = j + 1;\n res.push(`${line}${\" \".repeat(Math.max(3 - String(line).length, 0))}| ${lines[j]}`);\n const lineLength = lines[j].length;\n const newLineSeqLength = newlineSequences[j] && newlineSequences[j].length || 0;\n if (j === i) {\n const pad = start2 - (count - (lineLength + newLineSeqLength));\n const length = Math.max(1, end > count ? lineLength - pad : end - start2);\n res.push(` | ` + \" \".repeat(pad) + \"^\".repeat(length));\n } else if (j > i) {\n if (end > count) {\n const length = Math.max(Math.min(end - count, lineLength), 1);\n res.push(` | ` + \"^\".repeat(length));\n }\n count += lineLength + newLineSeqLength;\n }\n }\n break;\n }\n }\n return res.join(\"\\n\");\n }\n var specialBooleanAttrs = `itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly`;\n var isSpecialBooleanAttr = /* @__PURE__ */ makeMap(specialBooleanAttrs);\n var isBooleanAttr2 = /* @__PURE__ */ makeMap(specialBooleanAttrs + `,async,autofocus,autoplay,controls,default,defer,disabled,hidden,loop,open,required,reversed,scoped,seamless,checked,muted,multiple,selected`);\n var unsafeAttrCharRE = /[>/=\"'\\u0009\\u000a\\u000c\\u0020]/;\n var attrValidationCache = {};\n function isSSRSafeAttrName(name) {\n if (attrValidationCache.hasOwnProperty(name)) {\n return attrValidationCache[name];\n }\n const isUnsafe = unsafeAttrCharRE.test(name);\n if (isUnsafe) {\n console.error(`unsafe attribute name: ${name}`);\n }\n return attrValidationCache[name] = !isUnsafe;\n }\n var propsToAttrMap = {\n acceptCharset: \"accept-charset\",\n className: \"class\",\n htmlFor: \"for\",\n httpEquiv: \"http-equiv\"\n };\n var isNoUnitNumericStyleProp = /* @__PURE__ */ makeMap(`animation-iteration-count,border-image-outset,border-image-slice,border-image-width,box-flex,box-flex-group,box-ordinal-group,column-count,columns,flex,flex-grow,flex-positive,flex-shrink,flex-negative,flex-order,grid-row,grid-row-end,grid-row-span,grid-row-start,grid-column,grid-column-end,grid-column-span,grid-column-start,font-weight,line-clamp,line-height,opacity,order,orphans,tab-size,widows,z-index,zoom,fill-opacity,flood-opacity,stop-opacity,stroke-dasharray,stroke-dashoffset,stroke-miterlimit,stroke-opacity,stroke-width`);\n var isKnownAttr = /* @__PURE__ */ makeMap(`accept,accept-charset,accesskey,action,align,allow,alt,async,autocapitalize,autocomplete,autofocus,autoplay,background,bgcolor,border,buffered,capture,challenge,charset,checked,cite,class,code,codebase,color,cols,colspan,content,contenteditable,contextmenu,controls,coords,crossorigin,csp,data,datetime,decoding,default,defer,dir,dirname,disabled,download,draggable,dropzone,enctype,enterkeyhint,for,form,formaction,formenctype,formmethod,formnovalidate,formtarget,headers,height,hidden,high,href,hreflang,http-equiv,icon,id,importance,integrity,ismap,itemprop,keytype,kind,label,lang,language,loading,list,loop,low,manifest,max,maxlength,minlength,media,min,multiple,muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,preload,radiogroup,readonly,referrerpolicy,rel,required,reversed,rows,rowspan,sandbox,scope,scoped,selected,shape,size,sizes,slot,span,spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,target,title,translate,type,usemap,value,width,wrap`);\n function normalizeStyle(value) {\n if (isArray(value)) {\n const res = {};\n for (let i = 0; i < value.length; i++) {\n const item = value[i];\n const normalized = normalizeStyle(isString(item) ? parseStringStyle(item) : item);\n if (normalized) {\n for (const key in normalized) {\n res[key] = normalized[key];\n }\n }\n }\n return res;\n } else if (isObject(value)) {\n return value;\n }\n }\n var listDelimiterRE = /;(?![^(]*\\))/g;\n var propertyDelimiterRE = /:(.+)/;\n function parseStringStyle(cssText) {\n const ret = {};\n cssText.split(listDelimiterRE).forEach((item) => {\n if (item) {\n const tmp = item.split(propertyDelimiterRE);\n tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());\n }\n });\n return ret;\n }\n function stringifyStyle(styles) {\n let ret = \"\";\n if (!styles) {\n return ret;\n }\n for (const key in styles) {\n const value = styles[key];\n const normalizedKey = key.startsWith(`--`) ? key : hyphenate(key);\n if (isString(value) || typeof value === \"number\" && isNoUnitNumericStyleProp(normalizedKey)) {\n ret += `${normalizedKey}:${value};`;\n }\n }\n return ret;\n }\n function normalizeClass(value) {\n let res = \"\";\n if (isString(value)) {\n res = value;\n } else if (isArray(value)) {\n for (let i = 0; i < value.length; i++) {\n const normalized = normalizeClass(value[i]);\n if (normalized) {\n res += normalized + \" \";\n }\n }\n } else if (isObject(value)) {\n for (const name in value) {\n if (value[name]) {\n res += name + \" \";\n }\n }\n }\n return res.trim();\n }\n var HTML_TAGS = \"html,body,base,head,link,meta,style,title,address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,output,progress,select,textarea,details,dialog,menu,summary,template,blockquote,iframe,tfoot\";\n var SVG_TAGS = \"svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,feDistanceLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,text,textPath,title,tspan,unknown,use,view\";\n var VOID_TAGS = \"area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr\";\n var isHTMLTag = /* @__PURE__ */ makeMap(HTML_TAGS);\n var isSVGTag = /* @__PURE__ */ makeMap(SVG_TAGS);\n var isVoidTag = /* @__PURE__ */ makeMap(VOID_TAGS);\n var escapeRE = /[\"'&<>]/;\n function escapeHtml(string) {\n const str = \"\" + string;\n const match = escapeRE.exec(str);\n if (!match) {\n return str;\n }\n let html = \"\";\n let escaped;\n let index;\n let lastIndex = 0;\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34:\n escaped = \""\";\n break;\n case 38:\n escaped = \"&\";\n break;\n case 39:\n escaped = \"'\";\n break;\n case 60:\n escaped = \"<\";\n break;\n case 62:\n escaped = \">\";\n break;\n default:\n continue;\n }\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n lastIndex = index + 1;\n html += escaped;\n }\n return lastIndex !== index ? html + str.substring(lastIndex, index) : html;\n }\n var commentStripRE = /^-?>||--!>| looseEqual(item, val));\n }\n var toDisplayString = (val) => {\n return val == null ? \"\" : isObject(val) ? JSON.stringify(val, replacer, 2) : String(val);\n };\n var replacer = (_key, val) => {\n if (isMap(val)) {\n return {\n [`Map(${val.size})`]: [...val.entries()].reduce((entries, [key, val2]) => {\n entries[`${key} =>`] = val2;\n return entries;\n }, {})\n };\n } else if (isSet(val)) {\n return {\n [`Set(${val.size})`]: [...val.values()]\n };\n } else if (isObject(val) && !isArray(val) && !isPlainObject(val)) {\n return String(val);\n }\n return val;\n };\n var babelParserDefaultPlugins = [\n \"bigInt\",\n \"optionalChaining\",\n \"nullishCoalescingOperator\"\n ];\n var EMPTY_OBJ = Object.freeze({});\n var EMPTY_ARR = Object.freeze([]);\n var NOOP = () => {\n };\n var NO = () => false;\n var onRE = /^on[^a-z]/;\n var isOn = (key) => onRE.test(key);\n var isModelListener = (key) => key.startsWith(\"onUpdate:\");\n var extend = Object.assign;\n var remove = (arr, el) => {\n const i = arr.indexOf(el);\n if (i > -1) {\n arr.splice(i, 1);\n }\n };\n var hasOwnProperty = Object.prototype.hasOwnProperty;\n var hasOwn = (val, key) => hasOwnProperty.call(val, key);\n var isArray = Array.isArray;\n var isMap = (val) => toTypeString(val) === \"[object Map]\";\n var isSet = (val) => toTypeString(val) === \"[object Set]\";\n var isDate = (val) => val instanceof Date;\n var isFunction = (val) => typeof val === \"function\";\n var isString = (val) => typeof val === \"string\";\n var isSymbol = (val) => typeof val === \"symbol\";\n var isObject = (val) => val !== null && typeof val === \"object\";\n var isPromise = (val) => {\n return isObject(val) && isFunction(val.then) && isFunction(val.catch);\n };\n var objectToString = Object.prototype.toString;\n var toTypeString = (value) => objectToString.call(value);\n var toRawType = (value) => {\n return toTypeString(value).slice(8, -1);\n };\n var isPlainObject = (val) => toTypeString(val) === \"[object Object]\";\n var isIntegerKey = (key) => isString(key) && key !== \"NaN\" && key[0] !== \"-\" && \"\" + parseInt(key, 10) === key;\n var isReservedProp = /* @__PURE__ */ makeMap(\n // the leading comma is intentional so empty string \"\" is also included\n \",key,ref,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted\"\n );\n var cacheStringFunction = (fn) => {\n const cache = /* @__PURE__ */ Object.create(null);\n return (str) => {\n const hit = cache[str];\n return hit || (cache[str] = fn(str));\n };\n };\n var camelizeRE = /-(\\w)/g;\n var camelize = cacheStringFunction((str) => {\n return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : \"\");\n });\n var hyphenateRE = /\\B([A-Z])/g;\n var hyphenate = cacheStringFunction((str) => str.replace(hyphenateRE, \"-$1\").toLowerCase());\n var capitalize = cacheStringFunction((str) => str.charAt(0).toUpperCase() + str.slice(1));\n var toHandlerKey = cacheStringFunction((str) => str ? `on${capitalize(str)}` : ``);\n var hasChanged = (value, oldValue) => value !== oldValue && (value === value || oldValue === oldValue);\n var invokeArrayFns = (fns, arg) => {\n for (let i = 0; i < fns.length; i++) {\n fns[i](arg);\n }\n };\n var def = (obj, key, value) => {\n Object.defineProperty(obj, key, {\n configurable: true,\n enumerable: false,\n value\n });\n };\n var toNumber = (val) => {\n const n = parseFloat(val);\n return isNaN(n) ? val : n;\n };\n var _globalThis;\n var getGlobalThis = () => {\n return _globalThis || (_globalThis = typeof globalThis !== \"undefined\" ? globalThis : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : typeof global !== \"undefined\" ? global : {});\n };\n exports.EMPTY_ARR = EMPTY_ARR;\n exports.EMPTY_OBJ = EMPTY_OBJ;\n exports.NO = NO;\n exports.NOOP = NOOP;\n exports.PatchFlagNames = PatchFlagNames;\n exports.babelParserDefaultPlugins = babelParserDefaultPlugins;\n exports.camelize = camelize;\n exports.capitalize = capitalize;\n exports.def = def;\n exports.escapeHtml = escapeHtml;\n exports.escapeHtmlComment = escapeHtmlComment;\n exports.extend = extend;\n exports.generateCodeFrame = generateCodeFrame;\n exports.getGlobalThis = getGlobalThis;\n exports.hasChanged = hasChanged;\n exports.hasOwn = hasOwn;\n exports.hyphenate = hyphenate;\n exports.invokeArrayFns = invokeArrayFns;\n exports.isArray = isArray;\n exports.isBooleanAttr = isBooleanAttr2;\n exports.isDate = isDate;\n exports.isFunction = isFunction;\n exports.isGloballyWhitelisted = isGloballyWhitelisted;\n exports.isHTMLTag = isHTMLTag;\n exports.isIntegerKey = isIntegerKey;\n exports.isKnownAttr = isKnownAttr;\n exports.isMap = isMap;\n exports.isModelListener = isModelListener;\n exports.isNoUnitNumericStyleProp = isNoUnitNumericStyleProp;\n exports.isObject = isObject;\n exports.isOn = isOn;\n exports.isPlainObject = isPlainObject;\n exports.isPromise = isPromise;\n exports.isReservedProp = isReservedProp;\n exports.isSSRSafeAttrName = isSSRSafeAttrName;\n exports.isSVGTag = isSVGTag;\n exports.isSet = isSet;\n exports.isSpecialBooleanAttr = isSpecialBooleanAttr;\n exports.isString = isString;\n exports.isSymbol = isSymbol;\n exports.isVoidTag = isVoidTag;\n exports.looseEqual = looseEqual;\n exports.looseIndexOf = looseIndexOf;\n exports.makeMap = makeMap;\n exports.normalizeClass = normalizeClass;\n exports.normalizeStyle = normalizeStyle;\n exports.objectToString = objectToString;\n exports.parseStringStyle = parseStringStyle;\n exports.propsToAttrMap = propsToAttrMap;\n exports.remove = remove;\n exports.slotFlagsText = slotFlagsText;\n exports.stringifyStyle = stringifyStyle;\n exports.toDisplayString = toDisplayString;\n exports.toHandlerKey = toHandlerKey;\n exports.toNumber = toNumber;\n exports.toRawType = toRawType;\n exports.toTypeString = toTypeString;\n }\n});\n\n// node_modules/@vue/shared/index.js\nvar require_shared = __commonJS({\n \"node_modules/@vue/shared/index.js\"(exports, module2) {\n \"use strict\";\n if (false) {\n module2.exports = null;\n } else {\n module2.exports = require_shared_cjs();\n }\n }\n});\n\n// node_modules/@vue/reactivity/dist/reactivity.cjs.js\nvar require_reactivity_cjs = __commonJS({\n \"node_modules/@vue/reactivity/dist/reactivity.cjs.js\"(exports) {\n \"use strict\";\n Object.defineProperty(exports, \"__esModule\", { value: true });\n var shared = require_shared();\n var targetMap = /* @__PURE__ */ new WeakMap();\n var effectStack = [];\n var activeEffect;\n var ITERATE_KEY = Symbol(\"iterate\");\n var MAP_KEY_ITERATE_KEY = Symbol(\"Map key iterate\");\n function isEffect(fn) {\n return fn && fn._isEffect === true;\n }\n function effect3(fn, options = shared.EMPTY_OBJ) {\n if (isEffect(fn)) {\n fn = fn.raw;\n }\n const effect4 = createReactiveEffect(fn, options);\n if (!options.lazy) {\n effect4();\n }\n return effect4;\n }\n function stop2(effect4) {\n if (effect4.active) {\n cleanup(effect4);\n if (effect4.options.onStop) {\n effect4.options.onStop();\n }\n effect4.active = false;\n }\n }\n var uid = 0;\n function createReactiveEffect(fn, options) {\n const effect4 = function reactiveEffect() {\n if (!effect4.active) {\n return fn();\n }\n if (!effectStack.includes(effect4)) {\n cleanup(effect4);\n try {\n enableTracking();\n effectStack.push(effect4);\n activeEffect = effect4;\n return fn();\n } finally {\n effectStack.pop();\n resetTracking();\n activeEffect = effectStack[effectStack.length - 1];\n }\n }\n };\n effect4.id = uid++;\n effect4.allowRecurse = !!options.allowRecurse;\n effect4._isEffect = true;\n effect4.active = true;\n effect4.raw = fn;\n effect4.deps = [];\n effect4.options = options;\n return effect4;\n }\n function cleanup(effect4) {\n const { deps } = effect4;\n if (deps.length) {\n for (let i = 0; i < deps.length; i++) {\n deps[i].delete(effect4);\n }\n deps.length = 0;\n }\n }\n var shouldTrack = true;\n var trackStack = [];\n function pauseTracking() {\n trackStack.push(shouldTrack);\n shouldTrack = false;\n }\n function enableTracking() {\n trackStack.push(shouldTrack);\n shouldTrack = true;\n }\n function resetTracking() {\n const last = trackStack.pop();\n shouldTrack = last === void 0 ? true : last;\n }\n function track(target, type, key) {\n if (!shouldTrack || activeEffect === void 0) {\n return;\n }\n let depsMap = targetMap.get(target);\n if (!depsMap) {\n targetMap.set(target, depsMap = /* @__PURE__ */ new Map());\n }\n let dep = depsMap.get(key);\n if (!dep) {\n depsMap.set(key, dep = /* @__PURE__ */ new Set());\n }\n if (!dep.has(activeEffect)) {\n dep.add(activeEffect);\n activeEffect.deps.push(dep);\n if (activeEffect.options.onTrack) {\n activeEffect.options.onTrack({\n effect: activeEffect,\n target,\n type,\n key\n });\n }\n }\n }\n function trigger(target, type, key, newValue, oldValue, oldTarget) {\n const depsMap = targetMap.get(target);\n if (!depsMap) {\n return;\n }\n const effects = /* @__PURE__ */ new Set();\n const add2 = (effectsToAdd) => {\n if (effectsToAdd) {\n effectsToAdd.forEach((effect4) => {\n if (effect4 !== activeEffect || effect4.allowRecurse) {\n effects.add(effect4);\n }\n });\n }\n };\n if (type === \"clear\") {\n depsMap.forEach(add2);\n } else if (key === \"length\" && shared.isArray(target)) {\n depsMap.forEach((dep, key2) => {\n if (key2 === \"length\" || key2 >= newValue) {\n add2(dep);\n }\n });\n } else {\n if (key !== void 0) {\n add2(depsMap.get(key));\n }\n switch (type) {\n case \"add\":\n if (!shared.isArray(target)) {\n add2(depsMap.get(ITERATE_KEY));\n if (shared.isMap(target)) {\n add2(depsMap.get(MAP_KEY_ITERATE_KEY));\n }\n } else if (shared.isIntegerKey(key)) {\n add2(depsMap.get(\"length\"));\n }\n break;\n case \"delete\":\n if (!shared.isArray(target)) {\n add2(depsMap.get(ITERATE_KEY));\n if (shared.isMap(target)) {\n add2(depsMap.get(MAP_KEY_ITERATE_KEY));\n }\n }\n break;\n case \"set\":\n if (shared.isMap(target)) {\n add2(depsMap.get(ITERATE_KEY));\n }\n break;\n }\n }\n const run = (effect4) => {\n if (effect4.options.onTrigger) {\n effect4.options.onTrigger({\n effect: effect4,\n target,\n key,\n type,\n newValue,\n oldValue,\n oldTarget\n });\n }\n if (effect4.options.scheduler) {\n effect4.options.scheduler(effect4);\n } else {\n effect4();\n }\n };\n effects.forEach(run);\n }\n var isNonTrackableKeys = /* @__PURE__ */ shared.makeMap(`__proto__,__v_isRef,__isVue`);\n var builtInSymbols = new Set(Object.getOwnPropertyNames(Symbol).map((key) => Symbol[key]).filter(shared.isSymbol));\n var get2 = /* @__PURE__ */ createGetter();\n var shallowGet = /* @__PURE__ */ createGetter(false, true);\n var readonlyGet = /* @__PURE__ */ createGetter(true);\n var shallowReadonlyGet = /* @__PURE__ */ createGetter(true, true);\n var arrayInstrumentations = /* @__PURE__ */ createArrayInstrumentations();\n function createArrayInstrumentations() {\n const instrumentations = {};\n [\"includes\", \"indexOf\", \"lastIndexOf\"].forEach((key) => {\n instrumentations[key] = function(...args) {\n const arr = toRaw2(this);\n for (let i = 0, l = this.length; i < l; i++) {\n track(arr, \"get\", i + \"\");\n }\n const res = arr[key](...args);\n if (res === -1 || res === false) {\n return arr[key](...args.map(toRaw2));\n } else {\n return res;\n }\n };\n });\n [\"push\", \"pop\", \"shift\", \"unshift\", \"splice\"].forEach((key) => {\n instrumentations[key] = function(...args) {\n pauseTracking();\n const res = toRaw2(this)[key].apply(this, args);\n resetTracking();\n return res;\n };\n });\n return instrumentations;\n }\n function createGetter(isReadonly2 = false, shallow = false) {\n return function get3(target, key, receiver) {\n if (key === \"__v_isReactive\") {\n return !isReadonly2;\n } else if (key === \"__v_isReadonly\") {\n return isReadonly2;\n } else if (key === \"__v_raw\" && receiver === (isReadonly2 ? shallow ? shallowReadonlyMap : readonlyMap : shallow ? shallowReactiveMap : reactiveMap).get(target)) {\n return target;\n }\n const targetIsArray = shared.isArray(target);\n if (!isReadonly2 && targetIsArray && shared.hasOwn(arrayInstrumentations, key)) {\n return Reflect.get(arrayInstrumentations, key, receiver);\n }\n const res = Reflect.get(target, key, receiver);\n if (shared.isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {\n return res;\n }\n if (!isReadonly2) {\n track(target, \"get\", key);\n }\n if (shallow) {\n return res;\n }\n if (isRef(res)) {\n const shouldUnwrap = !targetIsArray || !shared.isIntegerKey(key);\n return shouldUnwrap ? res.value : res;\n }\n if (shared.isObject(res)) {\n return isReadonly2 ? readonly(res) : reactive3(res);\n }\n return res;\n };\n }\n var set2 = /* @__PURE__ */ createSetter();\n var shallowSet = /* @__PURE__ */ createSetter(true);\n function createSetter(shallow = false) {\n return function set3(target, key, value, receiver) {\n let oldValue = target[key];\n if (!shallow) {\n value = toRaw2(value);\n oldValue = toRaw2(oldValue);\n if (!shared.isArray(target) && isRef(oldValue) && !isRef(value)) {\n oldValue.value = value;\n return true;\n }\n }\n const hadKey = shared.isArray(target) && shared.isIntegerKey(key) ? Number(key) < target.length : shared.hasOwn(target, key);\n const result = Reflect.set(target, key, value, receiver);\n if (target === toRaw2(receiver)) {\n if (!hadKey) {\n trigger(target, \"add\", key, value);\n } else if (shared.hasChanged(value, oldValue)) {\n trigger(target, \"set\", key, value, oldValue);\n }\n }\n return result;\n };\n }\n function deleteProperty(target, key) {\n const hadKey = shared.hasOwn(target, key);\n const oldValue = target[key];\n const result = Reflect.deleteProperty(target, key);\n if (result && hadKey) {\n trigger(target, \"delete\", key, void 0, oldValue);\n }\n return result;\n }\n function has(target, key) {\n const result = Reflect.has(target, key);\n if (!shared.isSymbol(key) || !builtInSymbols.has(key)) {\n track(target, \"has\", key);\n }\n return result;\n }\n function ownKeys(target) {\n track(target, \"iterate\", shared.isArray(target) ? \"length\" : ITERATE_KEY);\n return Reflect.ownKeys(target);\n }\n var mutableHandlers = {\n get: get2,\n set: set2,\n deleteProperty,\n has,\n ownKeys\n };\n var readonlyHandlers = {\n get: readonlyGet,\n set(target, key) {\n {\n console.warn(`Set operation on key \"${String(key)}\" failed: target is readonly.`, target);\n }\n return true;\n },\n deleteProperty(target, key) {\n {\n console.warn(`Delete operation on key \"${String(key)}\" failed: target is readonly.`, target);\n }\n return true;\n }\n };\n var shallowReactiveHandlers = /* @__PURE__ */ shared.extend({}, mutableHandlers, {\n get: shallowGet,\n set: shallowSet\n });\n var shallowReadonlyHandlers = /* @__PURE__ */ shared.extend({}, readonlyHandlers, {\n get: shallowReadonlyGet\n });\n var toReactive = (value) => shared.isObject(value) ? reactive3(value) : value;\n var toReadonly = (value) => shared.isObject(value) ? readonly(value) : value;\n var toShallow = (value) => value;\n var getProto = (v) => Reflect.getPrototypeOf(v);\n function get$1(target, key, isReadonly2 = false, isShallow = false) {\n target = target[\n \"__v_raw\"\n /* RAW */\n ];\n const rawTarget = toRaw2(target);\n const rawKey = toRaw2(key);\n if (key !== rawKey) {\n !isReadonly2 && track(rawTarget, \"get\", key);\n }\n !isReadonly2 && track(rawTarget, \"get\", rawKey);\n const { has: has2 } = getProto(rawTarget);\n const wrap = isShallow ? toShallow : isReadonly2 ? toReadonly : toReactive;\n if (has2.call(rawTarget, key)) {\n return wrap(target.get(key));\n } else if (has2.call(rawTarget, rawKey)) {\n return wrap(target.get(rawKey));\n } else if (target !== rawTarget) {\n target.get(key);\n }\n }\n function has$1(key, isReadonly2 = false) {\n const target = this[\n \"__v_raw\"\n /* RAW */\n ];\n const rawTarget = toRaw2(target);\n const rawKey = toRaw2(key);\n if (key !== rawKey) {\n !isReadonly2 && track(rawTarget, \"has\", key);\n }\n !isReadonly2 && track(rawTarget, \"has\", rawKey);\n return key === rawKey ? target.has(key) : target.has(key) || target.has(rawKey);\n }\n function size(target, isReadonly2 = false) {\n target = target[\n \"__v_raw\"\n /* RAW */\n ];\n !isReadonly2 && track(toRaw2(target), \"iterate\", ITERATE_KEY);\n return Reflect.get(target, \"size\", target);\n }\n function add(value) {\n value = toRaw2(value);\n const target = toRaw2(this);\n const proto = getProto(target);\n const hadKey = proto.has.call(target, value);\n if (!hadKey) {\n target.add(value);\n trigger(target, \"add\", value, value);\n }\n return this;\n }\n function set$1(key, value) {\n value = toRaw2(value);\n const target = toRaw2(this);\n const { has: has2, get: get3 } = getProto(target);\n let hadKey = has2.call(target, key);\n if (!hadKey) {\n key = toRaw2(key);\n hadKey = has2.call(target, key);\n } else {\n checkIdentityKeys(target, has2, key);\n }\n const oldValue = get3.call(target, key);\n target.set(key, value);\n if (!hadKey) {\n trigger(target, \"add\", key, value);\n } else if (shared.hasChanged(value, oldValue)) {\n trigger(target, \"set\", key, value, oldValue);\n }\n return this;\n }\n function deleteEntry(key) {\n const target = toRaw2(this);\n const { has: has2, get: get3 } = getProto(target);\n let hadKey = has2.call(target, key);\n if (!hadKey) {\n key = toRaw2(key);\n hadKey = has2.call(target, key);\n } else {\n checkIdentityKeys(target, has2, key);\n }\n const oldValue = get3 ? get3.call(target, key) : void 0;\n const result = target.delete(key);\n if (hadKey) {\n trigger(target, \"delete\", key, void 0, oldValue);\n }\n return result;\n }\n function clear() {\n const target = toRaw2(this);\n const hadItems = target.size !== 0;\n const oldTarget = shared.isMap(target) ? new Map(target) : new Set(target);\n const result = target.clear();\n if (hadItems) {\n trigger(target, \"clear\", void 0, void 0, oldTarget);\n }\n return result;\n }\n function createForEach(isReadonly2, isShallow) {\n return function forEach(callback, thisArg) {\n const observed = this;\n const target = observed[\n \"__v_raw\"\n /* RAW */\n ];\n const rawTarget = toRaw2(target);\n const wrap = isShallow ? toShallow : isReadonly2 ? toReadonly : toReactive;\n !isReadonly2 && track(rawTarget, \"iterate\", ITERATE_KEY);\n return target.forEach((value, key) => {\n return callback.call(thisArg, wrap(value), wrap(key), observed);\n });\n };\n }\n function createIterableMethod(method, isReadonly2, isShallow) {\n return function(...args) {\n const target = this[\n \"__v_raw\"\n /* RAW */\n ];\n const rawTarget = toRaw2(target);\n const targetIsMap = shared.isMap(rawTarget);\n const isPair = method === \"entries\" || method === Symbol.iterator && targetIsMap;\n const isKeyOnly = method === \"keys\" && targetIsMap;\n const innerIterator = target[method](...args);\n const wrap = isShallow ? toShallow : isReadonly2 ? toReadonly : toReactive;\n !isReadonly2 && track(rawTarget, \"iterate\", isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY);\n return {\n // iterator protocol\n next() {\n const { value, done } = innerIterator.next();\n return done ? { value, done } : {\n value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value),\n done\n };\n },\n // iterable protocol\n [Symbol.iterator]() {\n return this;\n }\n };\n };\n }\n function createReadonlyMethod(type) {\n return function(...args) {\n {\n const key = args[0] ? `on key \"${args[0]}\" ` : ``;\n console.warn(`${shared.capitalize(type)} operation ${key}failed: target is readonly.`, toRaw2(this));\n }\n return type === \"delete\" ? false : this;\n };\n }\n function createInstrumentations() {\n const mutableInstrumentations2 = {\n get(key) {\n return get$1(this, key);\n },\n get size() {\n return size(this);\n },\n has: has$1,\n add,\n set: set$1,\n delete: deleteEntry,\n clear,\n forEach: createForEach(false, false)\n };\n const shallowInstrumentations2 = {\n get(key) {\n return get$1(this, key, false, true);\n },\n get size() {\n return size(this);\n },\n has: has$1,\n add,\n set: set$1,\n delete: deleteEntry,\n clear,\n forEach: createForEach(false, true)\n };\n const readonlyInstrumentations2 = {\n get(key) {\n return get$1(this, key, true);\n },\n get size() {\n return size(this, true);\n },\n has(key) {\n return has$1.call(this, key, true);\n },\n add: createReadonlyMethod(\n \"add\"\n /* ADD */\n ),\n set: createReadonlyMethod(\n \"set\"\n /* SET */\n ),\n delete: createReadonlyMethod(\n \"delete\"\n /* DELETE */\n ),\n clear: createReadonlyMethod(\n \"clear\"\n /* CLEAR */\n ),\n forEach: createForEach(true, false)\n };\n const shallowReadonlyInstrumentations2 = {\n get(key) {\n return get$1(this, key, true, true);\n },\n get size() {\n return size(this, true);\n },\n has(key) {\n return has$1.call(this, key, true);\n },\n add: createReadonlyMethod(\n \"add\"\n /* ADD */\n ),\n set: createReadonlyMethod(\n \"set\"\n /* SET */\n ),\n delete: createReadonlyMethod(\n \"delete\"\n /* DELETE */\n ),\n clear: createReadonlyMethod(\n \"clear\"\n /* CLEAR */\n ),\n forEach: createForEach(true, true)\n };\n const iteratorMethods = [\"keys\", \"values\", \"entries\", Symbol.iterator];\n iteratorMethods.forEach((method) => {\n mutableInstrumentations2[method] = createIterableMethod(method, false, false);\n readonlyInstrumentations2[method] = createIterableMethod(method, true, false);\n shallowInstrumentations2[method] = createIterableMethod(method, false, true);\n shallowReadonlyInstrumentations2[method] = createIterableMethod(method, true, true);\n });\n return [\n mutableInstrumentations2,\n readonlyInstrumentations2,\n shallowInstrumentations2,\n shallowReadonlyInstrumentations2\n ];\n }\n var [mutableInstrumentations, readonlyInstrumentations, shallowInstrumentations, shallowReadonlyInstrumentations] = /* @__PURE__ */ createInstrumentations();\n function createInstrumentationGetter(isReadonly2, shallow) {\n const instrumentations = shallow ? isReadonly2 ? shallowReadonlyInstrumentations : shallowInstrumentations : isReadonly2 ? readonlyInstrumentations : mutableInstrumentations;\n return (target, key, receiver) => {\n if (key === \"__v_isReactive\") {\n return !isReadonly2;\n } else if (key === \"__v_isReadonly\") {\n return isReadonly2;\n } else if (key === \"__v_raw\") {\n return target;\n }\n return Reflect.get(shared.hasOwn(instrumentations, key) && key in target ? instrumentations : target, key, receiver);\n };\n }\n var mutableCollectionHandlers = {\n get: /* @__PURE__ */ createInstrumentationGetter(false, false)\n };\n var shallowCollectionHandlers = {\n get: /* @__PURE__ */ createInstrumentationGetter(false, true)\n };\n var readonlyCollectionHandlers = {\n get: /* @__PURE__ */ createInstrumentationGetter(true, false)\n };\n var shallowReadonlyCollectionHandlers = {\n get: /* @__PURE__ */ createInstrumentationGetter(true, true)\n };\n function checkIdentityKeys(target, has2, key) {\n const rawKey = toRaw2(key);\n if (rawKey !== key && has2.call(target, rawKey)) {\n const type = shared.toRawType(target);\n console.warn(`Reactive ${type} contains both the raw and reactive versions of the same object${type === `Map` ? ` as keys` : ``}, which can lead to inconsistencies. Avoid differentiating between the raw and reactive versions of an object and only use the reactive version if possible.`);\n }\n }\n var reactiveMap = /* @__PURE__ */ new WeakMap();\n var shallowReactiveMap = /* @__PURE__ */ new WeakMap();\n var readonlyMap = /* @__PURE__ */ new WeakMap();\n var shallowReadonlyMap = /* @__PURE__ */ new WeakMap();\n function targetTypeMap(rawType) {\n switch (rawType) {\n case \"Object\":\n case \"Array\":\n return 1;\n case \"Map\":\n case \"Set\":\n case \"WeakMap\":\n case \"WeakSet\":\n return 2;\n default:\n return 0;\n }\n }\n function getTargetType(value) {\n return value[\n \"__v_skip\"\n /* SKIP */\n ] || !Object.isExtensible(value) ? 0 : targetTypeMap(shared.toRawType(value));\n }\n function reactive3(target) {\n if (target && target[\n \"__v_isReadonly\"\n /* IS_READONLY */\n ]) {\n return target;\n }\n return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);\n }\n function shallowReactive(target) {\n return createReactiveObject(target, false, shallowReactiveHandlers, shallowCollectionHandlers, shallowReactiveMap);\n }\n function readonly(target) {\n return createReactiveObject(target, true, readonlyHandlers, readonlyCollectionHandlers, readonlyMap);\n }\n function shallowReadonly(target) {\n return createReactiveObject(target, true, shallowReadonlyHandlers, shallowReadonlyCollectionHandlers, shallowReadonlyMap);\n }\n function createReactiveObject(target, isReadonly2, baseHandlers, collectionHandlers, proxyMap) {\n if (!shared.isObject(target)) {\n {\n console.warn(`value cannot be made reactive: ${String(target)}`);\n }\n return target;\n }\n if (target[\n \"__v_raw\"\n /* RAW */\n ] && !(isReadonly2 && target[\n \"__v_isReactive\"\n /* IS_REACTIVE */\n ])) {\n return target;\n }\n const existingProxy = proxyMap.get(target);\n if (existingProxy) {\n return existingProxy;\n }\n const targetType = getTargetType(target);\n if (targetType === 0) {\n return target;\n }\n const proxy = new Proxy(target, targetType === 2 ? collectionHandlers : baseHandlers);\n proxyMap.set(target, proxy);\n return proxy;\n }\n function isReactive2(value) {\n if (isReadonly(value)) {\n return isReactive2(value[\n \"__v_raw\"\n /* RAW */\n ]);\n }\n return !!(value && value[\n \"__v_isReactive\"\n /* IS_REACTIVE */\n ]);\n }\n function isReadonly(value) {\n return !!(value && value[\n \"__v_isReadonly\"\n /* IS_READONLY */\n ]);\n }\n function isProxy(value) {\n return isReactive2(value) || isReadonly(value);\n }\n function toRaw2(observed) {\n return observed && toRaw2(observed[\n \"__v_raw\"\n /* RAW */\n ]) || observed;\n }\n function markRaw(value) {\n shared.def(value, \"__v_skip\", true);\n return value;\n }\n var convert = (val) => shared.isObject(val) ? reactive3(val) : val;\n function isRef(r) {\n return Boolean(r && r.__v_isRef === true);\n }\n function ref(value) {\n return createRef(value);\n }\n function shallowRef(value) {\n return createRef(value, true);\n }\n var RefImpl = class {\n constructor(value, _shallow = false) {\n this._shallow = _shallow;\n this.__v_isRef = true;\n this._rawValue = _shallow ? value : toRaw2(value);\n this._value = _shallow ? value : convert(value);\n }\n get value() {\n track(toRaw2(this), \"get\", \"value\");\n return this._value;\n }\n set value(newVal) {\n newVal = this._shallow ? newVal : toRaw2(newVal);\n if (shared.hasChanged(newVal, this._rawValue)) {\n this._rawValue = newVal;\n this._value = this._shallow ? newVal : convert(newVal);\n trigger(toRaw2(this), \"set\", \"value\", newVal);\n }\n }\n };\n function createRef(rawValue, shallow = false) {\n if (isRef(rawValue)) {\n return rawValue;\n }\n return new RefImpl(rawValue, shallow);\n }\n function triggerRef(ref2) {\n trigger(toRaw2(ref2), \"set\", \"value\", ref2.value);\n }\n function unref(ref2) {\n return isRef(ref2) ? ref2.value : ref2;\n }\n var shallowUnwrapHandlers = {\n get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),\n set: (target, key, value, receiver) => {\n const oldValue = target[key];\n if (isRef(oldValue) && !isRef(value)) {\n oldValue.value = value;\n return true;\n } else {\n return Reflect.set(target, key, value, receiver);\n }\n }\n };\n function proxyRefs(objectWithRefs) {\n return isReactive2(objectWithRefs) ? objectWithRefs : new Proxy(objectWithRefs, shallowUnwrapHandlers);\n }\n var CustomRefImpl = class {\n constructor(factory) {\n this.__v_isRef = true;\n const { get: get3, set: set3 } = factory(() => track(this, \"get\", \"value\"), () => trigger(this, \"set\", \"value\"));\n this._get = get3;\n this._set = set3;\n }\n get value() {\n return this._get();\n }\n set value(newVal) {\n this._set(newVal);\n }\n };\n function customRef(factory) {\n return new CustomRefImpl(factory);\n }\n function toRefs(object) {\n if (!isProxy(object)) {\n console.warn(`toRefs() expects a reactive object but received a plain one.`);\n }\n const ret = shared.isArray(object) ? new Array(object.length) : {};\n for (const key in object) {\n ret[key] = toRef(object, key);\n }\n return ret;\n }\n var ObjectRefImpl = class {\n constructor(_object, _key) {\n this._object = _object;\n this._key = _key;\n this.__v_isRef = true;\n }\n get value() {\n return this._object[this._key];\n }\n set value(newVal) {\n this._object[this._key] = newVal;\n }\n };\n function toRef(object, key) {\n return isRef(object[key]) ? object[key] : new ObjectRefImpl(object, key);\n }\n var ComputedRefImpl = class {\n constructor(getter, _setter, isReadonly2) {\n this._setter = _setter;\n this._dirty = true;\n this.__v_isRef = true;\n this.effect = effect3(getter, {\n lazy: true,\n scheduler: () => {\n if (!this._dirty) {\n this._dirty = true;\n trigger(toRaw2(this), \"set\", \"value\");\n }\n }\n });\n this[\n \"__v_isReadonly\"\n /* IS_READONLY */\n ] = isReadonly2;\n }\n get value() {\n const self2 = toRaw2(this);\n if (self2._dirty) {\n self2._value = this.effect();\n self2._dirty = false;\n }\n track(self2, \"get\", \"value\");\n return self2._value;\n }\n set value(newValue) {\n this._setter(newValue);\n }\n };\n function computed(getterOrOptions) {\n let getter;\n let setter;\n if (shared.isFunction(getterOrOptions)) {\n getter = getterOrOptions;\n setter = () => {\n console.warn(\"Write operation failed: computed value is readonly\");\n };\n } else {\n getter = getterOrOptions.get;\n setter = getterOrOptions.set;\n }\n return new ComputedRefImpl(getter, setter, shared.isFunction(getterOrOptions) || !getterOrOptions.set);\n }\n exports.ITERATE_KEY = ITERATE_KEY;\n exports.computed = computed;\n exports.customRef = customRef;\n exports.effect = effect3;\n exports.enableTracking = enableTracking;\n exports.isProxy = isProxy;\n exports.isReactive = isReactive2;\n exports.isReadonly = isReadonly;\n exports.isRef = isRef;\n exports.markRaw = markRaw;\n exports.pauseTracking = pauseTracking;\n exports.proxyRefs = proxyRefs;\n exports.reactive = reactive3;\n exports.readonly = readonly;\n exports.ref = ref;\n exports.resetTracking = resetTracking;\n exports.shallowReactive = shallowReactive;\n exports.shallowReadonly = shallowReadonly;\n exports.shallowRef = shallowRef;\n exports.stop = stop2;\n exports.toRaw = toRaw2;\n exports.toRef = toRef;\n exports.toRefs = toRefs;\n exports.track = track;\n exports.trigger = trigger;\n exports.triggerRef = triggerRef;\n exports.unref = unref;\n }\n});\n\n// node_modules/@vue/reactivity/index.js\nvar require_reactivity = __commonJS({\n \"node_modules/@vue/reactivity/index.js\"(exports, module2) {\n \"use strict\";\n if (false) {\n module2.exports = null;\n } else {\n module2.exports = require_reactivity_cjs();\n }\n }\n});\n\n// packages/alpinejs/builds/module.js\nvar module_exports = {};\n__export(module_exports, {\n Alpine: () => src_default,\n default: () => module_default\n});\nmodule.exports = __toCommonJS(module_exports);\n\n// packages/alpinejs/src/scheduler.js\nvar flushPending = false;\nvar flushing = false;\nvar queue = [];\nvar lastFlushedIndex = -1;\nfunction scheduler(callback) {\n queueJob(callback);\n}\nfunction queueJob(job) {\n if (!queue.includes(job))\n queue.push(job);\n queueFlush();\n}\nfunction dequeueJob(job) {\n let index = queue.indexOf(job);\n if (index !== -1 && index > lastFlushedIndex)\n queue.splice(index, 1);\n}\nfunction queueFlush() {\n if (!flushing && !flushPending) {\n flushPending = true;\n queueMicrotask(flushJobs);\n }\n}\nfunction flushJobs() {\n flushPending = false;\n flushing = true;\n for (let i = 0; i < queue.length; i++) {\n queue[i]();\n lastFlushedIndex = i;\n }\n queue.length = 0;\n lastFlushedIndex = -1;\n flushing = false;\n}\n\n// packages/alpinejs/src/reactivity.js\nvar reactive;\nvar effect;\nvar release;\nvar raw;\nvar shouldSchedule = true;\nfunction disableEffectScheduling(callback) {\n shouldSchedule = false;\n callback();\n shouldSchedule = true;\n}\nfunction setReactivityEngine(engine) {\n reactive = engine.reactive;\n release = engine.release;\n effect = (callback) => engine.effect(callback, { scheduler: (task) => {\n if (shouldSchedule) {\n scheduler(task);\n } else {\n task();\n }\n } });\n raw = engine.raw;\n}\nfunction overrideEffect(override) {\n effect = override;\n}\nfunction elementBoundEffect(el) {\n let cleanup = () => {\n };\n let wrappedEffect = (callback) => {\n let effectReference = effect(callback);\n if (!el._x_effects) {\n el._x_effects = /* @__PURE__ */ new Set();\n el._x_runEffects = () => {\n el._x_effects.forEach((i) => i());\n };\n }\n el._x_effects.add(effectReference);\n cleanup = () => {\n if (effectReference === void 0)\n return;\n el._x_effects.delete(effectReference);\n release(effectReference);\n };\n return effectReference;\n };\n return [wrappedEffect, () => {\n cleanup();\n }];\n}\nfunction watch(getter, callback) {\n let firstTime = true;\n let oldValue;\n let effectReference = effect(() => {\n let value = getter();\n JSON.stringify(value);\n if (!firstTime) {\n queueMicrotask(() => {\n callback(value, oldValue);\n oldValue = value;\n });\n } else {\n oldValue = value;\n }\n firstTime = false;\n });\n return () => release(effectReference);\n}\n\n// packages/alpinejs/src/mutation.js\nvar onAttributeAddeds = [];\nvar onElRemoveds = [];\nvar onElAddeds = [];\nfunction onElAdded(callback) {\n onElAddeds.push(callback);\n}\nfunction onElRemoved(el, callback) {\n if (typeof callback === \"function\") {\n if (!el._x_cleanups)\n el._x_cleanups = [];\n el._x_cleanups.push(callback);\n } else {\n callback = el;\n onElRemoveds.push(callback);\n }\n}\nfunction onAttributesAdded(callback) {\n onAttributeAddeds.push(callback);\n}\nfunction onAttributeRemoved(el, name, callback) {\n if (!el._x_attributeCleanups)\n el._x_attributeCleanups = {};\n if (!el._x_attributeCleanups[name])\n el._x_attributeCleanups[name] = [];\n el._x_attributeCleanups[name].push(callback);\n}\nfunction cleanupAttributes(el, names) {\n if (!el._x_attributeCleanups)\n return;\n Object.entries(el._x_attributeCleanups).forEach(([name, value]) => {\n if (names === void 0 || names.includes(name)) {\n value.forEach((i) => i());\n delete el._x_attributeCleanups[name];\n }\n });\n}\nfunction cleanupElement(el) {\n var _a, _b;\n (_a = el._x_effects) == null ? void 0 : _a.forEach(dequeueJob);\n while ((_b = el._x_cleanups) == null ? void 0 : _b.length)\n el._x_cleanups.pop()();\n}\nvar observer = new MutationObserver(onMutate);\nvar currentlyObserving = false;\nfunction startObservingMutations() {\n observer.observe(document, { subtree: true, childList: true, attributes: true, attributeOldValue: true });\n currentlyObserving = true;\n}\nfunction stopObservingMutations() {\n flushObserver();\n observer.disconnect();\n currentlyObserving = false;\n}\nvar queuedMutations = [];\nfunction flushObserver() {\n let records = observer.takeRecords();\n queuedMutations.push(() => records.length > 0 && onMutate(records));\n let queueLengthWhenTriggered = queuedMutations.length;\n queueMicrotask(() => {\n if (queuedMutations.length === queueLengthWhenTriggered) {\n while (queuedMutations.length > 0)\n queuedMutations.shift()();\n }\n });\n}\nfunction mutateDom(callback) {\n if (!currentlyObserving)\n return callback();\n stopObservingMutations();\n let result = callback();\n startObservingMutations();\n return result;\n}\nvar isCollecting = false;\nvar deferredMutations = [];\nfunction deferMutations() {\n isCollecting = true;\n}\nfunction flushAndStopDeferringMutations() {\n isCollecting = false;\n onMutate(deferredMutations);\n deferredMutations = [];\n}\nfunction onMutate(mutations) {\n if (isCollecting) {\n deferredMutations = deferredMutations.concat(mutations);\n return;\n }\n let addedNodes = /* @__PURE__ */ new Set();\n let removedNodes = /* @__PURE__ */ new Set();\n let addedAttributes = /* @__PURE__ */ new Map();\n let removedAttributes = /* @__PURE__ */ new Map();\n for (let i = 0; i < mutations.length; i++) {\n if (mutations[i].target._x_ignoreMutationObserver)\n continue;\n if (mutations[i].type === \"childList\") {\n mutations[i].addedNodes.forEach((node) => node.nodeType === 1 && addedNodes.add(node));\n mutations[i].removedNodes.forEach((node) => node.nodeType === 1 && removedNodes.add(node));\n }\n if (mutations[i].type === \"attributes\") {\n let el = mutations[i].target;\n let name = mutations[i].attributeName;\n let oldValue = mutations[i].oldValue;\n let add = () => {\n if (!addedAttributes.has(el))\n addedAttributes.set(el, []);\n addedAttributes.get(el).push({ name, value: el.getAttribute(name) });\n };\n let remove = () => {\n if (!removedAttributes.has(el))\n removedAttributes.set(el, []);\n removedAttributes.get(el).push(name);\n };\n if (el.hasAttribute(name) && oldValue === null) {\n add();\n } else if (el.hasAttribute(name)) {\n remove();\n add();\n } else {\n remove();\n }\n }\n }\n removedAttributes.forEach((attrs, el) => {\n cleanupAttributes(el, attrs);\n });\n addedAttributes.forEach((attrs, el) => {\n onAttributeAddeds.forEach((i) => i(el, attrs));\n });\n for (let node of removedNodes) {\n if (addedNodes.has(node))\n continue;\n onElRemoveds.forEach((i) => i(node));\n }\n addedNodes.forEach((node) => {\n node._x_ignoreSelf = true;\n node._x_ignore = true;\n });\n for (let node of addedNodes) {\n if (removedNodes.has(node))\n continue;\n if (!node.isConnected)\n continue;\n delete node._x_ignoreSelf;\n delete node._x_ignore;\n onElAddeds.forEach((i) => i(node));\n node._x_ignore = true;\n node._x_ignoreSelf = true;\n }\n addedNodes.forEach((node) => {\n delete node._x_ignoreSelf;\n delete node._x_ignore;\n });\n addedNodes = null;\n removedNodes = null;\n addedAttributes = null;\n removedAttributes = null;\n}\n\n// packages/alpinejs/src/scope.js\nfunction scope(node) {\n return mergeProxies(closestDataStack(node));\n}\nfunction addScopeToNode(node, data2, referenceNode) {\n node._x_dataStack = [data2, ...closestDataStack(referenceNode || node)];\n return () => {\n node._x_dataStack = node._x_dataStack.filter((i) => i !== data2);\n };\n}\nfunction closestDataStack(node) {\n if (node._x_dataStack)\n return node._x_dataStack;\n if (typeof ShadowRoot === \"function\" && node instanceof ShadowRoot) {\n return closestDataStack(node.host);\n }\n if (!node.parentNode) {\n return [];\n }\n return closestDataStack(node.parentNode);\n}\nfunction mergeProxies(objects) {\n return new Proxy({ objects }, mergeProxyTrap);\n}\nvar mergeProxyTrap = {\n ownKeys({ objects }) {\n return Array.from(\n new Set(objects.flatMap((i) => Object.keys(i)))\n );\n },\n has({ objects }, name) {\n if (name == Symbol.unscopables)\n return false;\n return objects.some(\n (obj) => Object.prototype.hasOwnProperty.call(obj, name) || Reflect.has(obj, name)\n );\n },\n get({ objects }, name, thisProxy) {\n if (name == \"toJSON\")\n return collapseProxies;\n return Reflect.get(\n objects.find(\n (obj) => Reflect.has(obj, name)\n ) || {},\n name,\n thisProxy\n );\n },\n set({ objects }, name, value, thisProxy) {\n const target = objects.find(\n (obj) => Object.prototype.hasOwnProperty.call(obj, name)\n ) || objects[objects.length - 1];\n const descriptor = Object.getOwnPropertyDescriptor(target, name);\n if ((descriptor == null ? void 0 : descriptor.set) && (descriptor == null ? void 0 : descriptor.get))\n return descriptor.set.call(thisProxy, value) || true;\n return Reflect.set(target, name, value);\n }\n};\nfunction collapseProxies() {\n let keys = Reflect.ownKeys(this);\n return keys.reduce((acc, key) => {\n acc[key] = Reflect.get(this, key);\n return acc;\n }, {});\n}\n\n// packages/alpinejs/src/interceptor.js\nfunction initInterceptors(data2) {\n let isObject = (val) => typeof val === \"object\" && !Array.isArray(val) && val !== null;\n let recurse = (obj, basePath = \"\") => {\n Object.entries(Object.getOwnPropertyDescriptors(obj)).forEach(([key, { value, enumerable }]) => {\n if (enumerable === false || value === void 0)\n return;\n if (typeof value === \"object\" && value !== null && value.__v_skip)\n return;\n let path = basePath === \"\" ? key : `${basePath}.${key}`;\n if (typeof value === \"object\" && value !== null && value._x_interceptor) {\n obj[key] = value.initialize(data2, path, key);\n } else {\n if (isObject(value) && value !== obj && !(value instanceof Element)) {\n recurse(value, path);\n }\n }\n });\n };\n return recurse(data2);\n}\nfunction interceptor(callback, mutateObj = () => {\n}) {\n let obj = {\n initialValue: void 0,\n _x_interceptor: true,\n initialize(data2, path, key) {\n return callback(this.initialValue, () => get(data2, path), (value) => set(data2, path, value), path, key);\n }\n };\n mutateObj(obj);\n return (initialValue) => {\n if (typeof initialValue === \"object\" && initialValue !== null && initialValue._x_interceptor) {\n let initialize = obj.initialize.bind(obj);\n obj.initialize = (data2, path, key) => {\n let innerValue = initialValue.initialize(data2, path, key);\n obj.initialValue = innerValue;\n return initialize(data2, path, key);\n };\n } else {\n obj.initialValue = initialValue;\n }\n return obj;\n };\n}\nfunction get(obj, path) {\n return path.split(\".\").reduce((carry, segment) => carry[segment], obj);\n}\nfunction set(obj, path, value) {\n if (typeof path === \"string\")\n path = path.split(\".\");\n if (path.length === 1)\n obj[path[0]] = value;\n else if (path.length === 0)\n throw error;\n else {\n if (obj[path[0]])\n return set(obj[path[0]], path.slice(1), value);\n else {\n obj[path[0]] = {};\n return set(obj[path[0]], path.slice(1), value);\n }\n }\n}\n\n// packages/alpinejs/src/magics.js\nvar magics = {};\nfunction magic(name, callback) {\n magics[name] = callback;\n}\nfunction injectMagics(obj, el) {\n let memoizedUtilities = getUtilities(el);\n Object.entries(magics).forEach(([name, callback]) => {\n Object.defineProperty(obj, `$${name}`, {\n get() {\n return callback(el, memoizedUtilities);\n },\n enumerable: false\n });\n });\n return obj;\n}\nfunction getUtilities(el) {\n let [utilities, cleanup] = getElementBoundUtilities(el);\n let utils = { interceptor, ...utilities };\n onElRemoved(el, cleanup);\n return utils;\n}\n\n// packages/alpinejs/src/utils/error.js\nfunction tryCatch(el, expression, callback, ...args) {\n try {\n return callback(...args);\n } catch (e) {\n handleError(e, el, expression);\n }\n}\nfunction handleError(error2, el, expression = void 0) {\n error2 = Object.assign(\n error2 != null ? error2 : { message: \"No error message given.\" },\n { el, expression }\n );\n console.warn(`Alpine Expression Error: ${error2.message}\n\n${expression ? 'Expression: \"' + expression + '\"\\n\\n' : \"\"}`, el);\n setTimeout(() => {\n throw error2;\n }, 0);\n}\n\n// packages/alpinejs/src/evaluator.js\nvar shouldAutoEvaluateFunctions = true;\nfunction dontAutoEvaluateFunctions(callback) {\n let cache = shouldAutoEvaluateFunctions;\n shouldAutoEvaluateFunctions = false;\n let result = callback();\n shouldAutoEvaluateFunctions = cache;\n return result;\n}\nfunction evaluate(el, expression, extras = {}) {\n let result;\n evaluateLater(el, expression)((value) => result = value, extras);\n return result;\n}\nfunction evaluateLater(...args) {\n return theEvaluatorFunction(...args);\n}\nvar theEvaluatorFunction = normalEvaluator;\nfunction setEvaluator(newEvaluator) {\n theEvaluatorFunction = newEvaluator;\n}\nfunction normalEvaluator(el, expression) {\n let overriddenMagics = {};\n injectMagics(overriddenMagics, el);\n let dataStack = [overriddenMagics, ...closestDataStack(el)];\n let evaluator = typeof expression === \"function\" ? generateEvaluatorFromFunction(dataStack, expression) : generateEvaluatorFromString(dataStack, expression, el);\n return tryCatch.bind(null, el, expression, evaluator);\n}\nfunction generateEvaluatorFromFunction(dataStack, func) {\n return (receiver = () => {\n }, { scope: scope2 = {}, params = [] } = {}) => {\n let result = func.apply(mergeProxies([scope2, ...dataStack]), params);\n runIfTypeOfFunction(receiver, result);\n };\n}\nvar evaluatorMemo = {};\nfunction generateFunctionFromString(expression, el) {\n if (evaluatorMemo[expression]) {\n return evaluatorMemo[expression];\n }\n let AsyncFunction = Object.getPrototypeOf(async function() {\n }).constructor;\n let rightSideSafeExpression = /^[\\n\\s]*if.*\\(.*\\)/.test(expression.trim()) || /^(let|const)\\s/.test(expression.trim()) ? `(async()=>{ ${expression} })()` : expression;\n const safeAsyncFunction = () => {\n try {\n let func2 = new AsyncFunction(\n [\"__self\", \"scope\"],\n `with (scope) { __self.result = ${rightSideSafeExpression} }; __self.finished = true; return __self.result;`\n );\n Object.defineProperty(func2, \"name\", {\n value: `[Alpine] ${expression}`\n });\n return func2;\n } catch (error2) {\n handleError(error2, el, expression);\n return Promise.resolve();\n }\n };\n let func = safeAsyncFunction();\n evaluatorMemo[expression] = func;\n return func;\n}\nfunction generateEvaluatorFromString(dataStack, expression, el) {\n let func = generateFunctionFromString(expression, el);\n return (receiver = () => {\n }, { scope: scope2 = {}, params = [] } = {}) => {\n func.result = void 0;\n func.finished = false;\n let completeScope = mergeProxies([scope2, ...dataStack]);\n if (typeof func === \"function\") {\n let promise = func(func, completeScope).catch((error2) => handleError(error2, el, expression));\n if (func.finished) {\n runIfTypeOfFunction(receiver, func.result, completeScope, params, el);\n func.result = void 0;\n } else {\n promise.then((result) => {\n runIfTypeOfFunction(receiver, result, completeScope, params, el);\n }).catch((error2) => handleError(error2, el, expression)).finally(() => func.result = void 0);\n }\n }\n };\n}\nfunction runIfTypeOfFunction(receiver, value, scope2, params, el) {\n if (shouldAutoEvaluateFunctions && typeof value === \"function\") {\n let result = value.apply(scope2, params);\n if (result instanceof Promise) {\n result.then((i) => runIfTypeOfFunction(receiver, i, scope2, params)).catch((error2) => handleError(error2, el, value));\n } else {\n receiver(result);\n }\n } else if (typeof value === \"object\" && value instanceof Promise) {\n value.then((i) => receiver(i));\n } else {\n receiver(value);\n }\n}\n\n// packages/alpinejs/src/directives.js\nvar prefixAsString = \"x-\";\nfunction prefix(subject = \"\") {\n return prefixAsString + subject;\n}\nfunction setPrefix(newPrefix) {\n prefixAsString = newPrefix;\n}\nvar directiveHandlers = {};\nfunction directive(name, callback) {\n directiveHandlers[name] = callback;\n return {\n before(directive2) {\n if (!directiveHandlers[directive2]) {\n console.warn(String.raw`Cannot find directive \\`${directive2}\\`. \\`${name}\\` will use the default order of execution`);\n return;\n }\n const pos = directiveOrder.indexOf(directive2);\n directiveOrder.splice(pos >= 0 ? pos : directiveOrder.indexOf(\"DEFAULT\"), 0, name);\n }\n };\n}\nfunction directiveExists(name) {\n return Object.keys(directiveHandlers).includes(name);\n}\nfunction directives(el, attributes, originalAttributeOverride) {\n attributes = Array.from(attributes);\n if (el._x_virtualDirectives) {\n let vAttributes = Object.entries(el._x_virtualDirectives).map(([name, value]) => ({ name, value }));\n let staticAttributes = attributesOnly(vAttributes);\n vAttributes = vAttributes.map((attribute) => {\n if (staticAttributes.find((attr) => attr.name === attribute.name)) {\n return {\n name: `x-bind:${attribute.name}`,\n value: `\"${attribute.value}\"`\n };\n }\n return attribute;\n });\n attributes = attributes.concat(vAttributes);\n }\n let transformedAttributeMap = {};\n let directives2 = attributes.map(toTransformedAttributes((newName, oldName) => transformedAttributeMap[newName] = oldName)).filter(outNonAlpineAttributes).map(toParsedDirectives(transformedAttributeMap, originalAttributeOverride)).sort(byPriority);\n return directives2.map((directive2) => {\n return getDirectiveHandler(el, directive2);\n });\n}\nfunction attributesOnly(attributes) {\n return Array.from(attributes).map(toTransformedAttributes()).filter((attr) => !outNonAlpineAttributes(attr));\n}\nvar isDeferringHandlers = false;\nvar directiveHandlerStacks = /* @__PURE__ */ new Map();\nvar currentHandlerStackKey = Symbol();\nfunction deferHandlingDirectives(callback) {\n isDeferringHandlers = true;\n let key = Symbol();\n currentHandlerStackKey = key;\n directiveHandlerStacks.set(key, []);\n let flushHandlers = () => {\n while (directiveHandlerStacks.get(key).length)\n directiveHandlerStacks.get(key).shift()();\n directiveHandlerStacks.delete(key);\n };\n let stopDeferring = () => {\n isDeferringHandlers = false;\n flushHandlers();\n };\n callback(flushHandlers);\n stopDeferring();\n}\nfunction getElementBoundUtilities(el) {\n let cleanups = [];\n let cleanup = (callback) => cleanups.push(callback);\n let [effect3, cleanupEffect] = elementBoundEffect(el);\n cleanups.push(cleanupEffect);\n let utilities = {\n Alpine: alpine_default,\n effect: effect3,\n cleanup,\n evaluateLater: evaluateLater.bind(evaluateLater, el),\n evaluate: evaluate.bind(evaluate, el)\n };\n let doCleanup = () => cleanups.forEach((i) => i());\n return [utilities, doCleanup];\n}\nfunction getDirectiveHandler(el, directive2) {\n let noop = () => {\n };\n let handler4 = directiveHandlers[directive2.type] || noop;\n let [utilities, cleanup] = getElementBoundUtilities(el);\n onAttributeRemoved(el, directive2.original, cleanup);\n let fullHandler = () => {\n if (el._x_ignore || el._x_ignoreSelf)\n return;\n handler4.inline && handler4.inline(el, directive2, utilities);\n handler4 = handler4.bind(handler4, el, directive2, utilities);\n isDeferringHandlers ? directiveHandlerStacks.get(currentHandlerStackKey).push(handler4) : handler4();\n };\n fullHandler.runCleanups = cleanup;\n return fullHandler;\n}\nvar startingWith = (subject, replacement) => ({ name, value }) => {\n if (name.startsWith(subject))\n name = name.replace(subject, replacement);\n return { name, value };\n};\nvar into = (i) => i;\nfunction toTransformedAttributes(callback = () => {\n}) {\n return ({ name, value }) => {\n let { name: newName, value: newValue } = attributeTransformers.reduce((carry, transform) => {\n return transform(carry);\n }, { name, value });\n if (newName !== name)\n callback(newName, name);\n return { name: newName, value: newValue };\n };\n}\nvar attributeTransformers = [];\nfunction mapAttributes(callback) {\n attributeTransformers.push(callback);\n}\nfunction outNonAlpineAttributes({ name }) {\n return alpineAttributeRegex().test(name);\n}\nvar alpineAttributeRegex = () => new RegExp(`^${prefixAsString}([^:^.]+)\\\\b`);\nfunction toParsedDirectives(transformedAttributeMap, originalAttributeOverride) {\n return ({ name, value }) => {\n let typeMatch = name.match(alpineAttributeRegex());\n let valueMatch = name.match(/:([a-zA-Z0-9\\-_:]+)/);\n let modifiers = name.match(/\\.[^.\\]]+(?=[^\\]]*$)/g) || [];\n let original = originalAttributeOverride || transformedAttributeMap[name] || name;\n return {\n type: typeMatch ? typeMatch[1] : null,\n value: valueMatch ? valueMatch[1] : null,\n modifiers: modifiers.map((i) => i.replace(\".\", \"\")),\n expression: value,\n original\n };\n };\n}\nvar DEFAULT = \"DEFAULT\";\nvar directiveOrder = [\n \"ignore\",\n \"ref\",\n \"data\",\n \"id\",\n \"anchor\",\n \"bind\",\n \"init\",\n \"for\",\n \"model\",\n \"modelable\",\n \"transition\",\n \"show\",\n \"if\",\n DEFAULT,\n \"teleport\"\n];\nfunction byPriority(a, b) {\n let typeA = directiveOrder.indexOf(a.type) === -1 ? DEFAULT : a.type;\n let typeB = directiveOrder.indexOf(b.type) === -1 ? DEFAULT : b.type;\n return directiveOrder.indexOf(typeA) - directiveOrder.indexOf(typeB);\n}\n\n// packages/alpinejs/src/utils/dispatch.js\nfunction dispatch(el, name, detail = {}) {\n el.dispatchEvent(\n new CustomEvent(name, {\n detail,\n bubbles: true,\n // Allows events to pass the shadow DOM barrier.\n composed: true,\n cancelable: true\n })\n );\n}\n\n// packages/alpinejs/src/utils/walk.js\nfunction walk(el, callback) {\n if (typeof ShadowRoot === \"function\" && el instanceof ShadowRoot) {\n Array.from(el.children).forEach((el2) => walk(el2, callback));\n return;\n }\n let skip = false;\n callback(el, () => skip = true);\n if (skip)\n return;\n let node = el.firstElementChild;\n while (node) {\n walk(node, callback, false);\n node = node.nextElementSibling;\n }\n}\n\n// packages/alpinejs/src/utils/warn.js\nfunction warn(message, ...args) {\n console.warn(`Alpine Warning: ${message}`, ...args);\n}\n\n// packages/alpinejs/src/lifecycle.js\nvar started = false;\nfunction start() {\n if (started)\n warn(\"Alpine has already been initialized on this page. Calling Alpine.start() more than once can cause problems.\");\n started = true;\n if (!document.body)\n warn(\"Unable to initialize. Trying to load Alpine before `` is available. Did you forget to add `defer` in Alpine's `
@stop
diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php
index 9eb2eb538..aa9c4e1d8 100755
--- a/resources/views/dashboard.blade.php
+++ b/resources/views/dashboard.blade.php
@@ -238,7 +238,7 @@
{{ trans('admin/hardware/table.icon') }} |
{{ trans('general.date') }} |
- {{ trans('general.admin') }} |
+ {{ trans('general.created_by') }} |
{{ trans('general.action') }} |
{{ trans('general.item') }} |
{{ trans('general.target') }} |
diff --git a/resources/views/departments/edit.blade.php b/resources/views/departments/edit.blade.php
index c1a2b38d5..05d9b49e4 100644
--- a/resources/views/departments/edit.blade.php
+++ b/resources/views/departments/edit.blade.php
@@ -26,6 +26,20 @@
@include ('partials.forms.edit.location-select', ['translated_name' => trans('general.location'), 'fieldname' => 'location_id'])
@include ('partials.forms.edit.image-upload', ['image_path' => app('departments_upload_path')])
+
@stop
diff --git a/resources/views/departments/index.blade.php b/resources/views/departments/index.blade.php
index aa9d09e66..d05d5c9d7 100644
--- a/resources/views/departments/index.blade.php
+++ b/resources/views/departments/index.blade.php
@@ -43,6 +43,7 @@
{{ trans('admin/departments/table.manager') }} |
{{ trans('general.users') }} |
{{ trans('admin/departments/table.location') }} |
+ {{ trans('general.notes') }} |
{{ trans('table.actions') }} |
diff --git a/resources/views/depreciations/view.blade.php b/resources/views/depreciations/view.blade.php
index b49bf0b9e..8dda5e64f 100644
--- a/resources/views/depreciations/view.blade.php
+++ b/resources/views/depreciations/view.blade.php
@@ -129,13 +129,8 @@
- {{ Form::open(
- [
- 'method' => 'POST',
- 'route' => ['models.bulkedit.index'],
- 'class' => 'form-inline',
- 'id' => 'bulkForm']
- ) }}
+
diff --git a/resources/views/groups/edit.blade.php b/resources/views/groups/edit.blade.php
index cb95b3762..4c387f73e 100755
--- a/resources/views/groups/edit.blade.php
+++ b/resources/views/groups/edit.blade.php
@@ -54,12 +54,29 @@
@section('inputFields')
@@ -82,4 +79,4 @@
@include ('partials.bootstrap-table')
-@stop
\ No newline at end of file
+@stop
diff --git a/resources/views/users/view.blade.php b/resources/views/users/view.blade.php
index 0d53c7ad2..e77ab3bd2 100755
--- a/resources/views/users/view.blade.php
+++ b/resources/views/users/view.blade.php
@@ -12,7 +12,21 @@
+
+ @if ($user->deleted_at!='')
+
+
+
+ {{ trans('admin/users/message.user_deleted_warning') }}
+
+
+ @endif
+
+
+
+
+
@@ -115,27 +129,6 @@
@can('update', $user)
- -
-
-
-
-
-
- {{ trans('button.actions') }}
-
-
-
-
-
- @endcan
-
- @can('update', \App\Models\User::class)
-
@@ -150,15 +143,6 @@
-
- @if ($user->deleted_at!='')
-
-
-
- {{ trans('admin/users/message.user_deleted_warning') }}
-
-
- @endif
@@ -180,7 +164,7 @@
@can('update', $user)
+
+
- {{ trans('general.name') }} |
+ {{ trans('general.name') }} |
{{ trans('admin/licenses/form.license_key') }} |
{{ trans('general.purchase_cost') }} |
{{ trans('admin/licenses/form.purchase_order') }} |
@@ -847,7 +833,7 @@
@can('viewKeys', $license)
- {!! $license->present()->serialUrl() !!}
+ {{ $license->serial }}
@else
------------
@endcan
@@ -863,7 +849,7 @@
|
@can('update', $license)
- {{ trans('general.checkin') }}
+ {{ trans('general.checkin') }}
@endcan
|
@@ -1017,7 +1003,7 @@
{{ trans('general.signature') }} |
@endif
{{ trans('admin/hardware/table.serial') }} |
- {{ trans('general.admin') }} |
+ {{ trans('general.created_by') }} |
{{ trans('admin/settings/general.login_ip') }} |
{{ trans('admin/settings/general.login_user_agent') }} |
{{ trans('general.action_source') }} |
@@ -1044,7 +1030,6 @@
data-bulk-button-id="#bulkLocationsEditButton"
data-bulk-form-id="#locationsBulkForm"
data-search="true"
- data-show-footer="true"
data-side-pagination="server"
data-show-columns="true"
data-show-fullscreen="true"
@@ -1077,7 +1062,6 @@
data-bulk-button-id="#bulkUserEditButton"
data-bulk-form-id="#usersBulkForm"
data-search="true"
- data-show-footer="true"
data-side-pagination="server"
data-show-columns="true"
data-show-fullscreen="true"
diff --git a/routes/api.php b/routes/api.php
index b51e139a4..5724990c7 100644
--- a/routes/api.php
+++ b/routes/api.php
@@ -40,6 +40,9 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'throttle:api']], functi
]
)->name('api.assets.requested');
+ Route::post('request/{asset}', [Api\CheckoutRequest::class, 'store'])->name('api.assets.requests.store');
+ Route::post('request/{asset}/cancel', [Api\CheckoutRequest::class, 'destroy'])->name('api.assets.requests.destroy');
+
Route::get('requestable/hardware',
[
Api\AssetsController::class,
@@ -508,8 +511,17 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'throttle:api']], functi
->where(['action' => 'audit|audits|checkins', 'upcoming_status' => 'due|overdue|due-or-overdue']);
+ // Legacy URL for audit
+ Route::post('audit',
+ [
+ Api\AssetsController::class,
+ 'audit'
+ ]
+ )->name('api.asset.audit.legacy');
- Route::post('audit',
+
+ // Newer url for audit
+ Route::post('{asset}/audit',
[
Api\AssetsController::class,
'audit'
@@ -1302,20 +1314,6 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'throttle:api']], functi
)->name('api.activity.index');
}); // end reports api routes
- /**
- * Notes API routes
- */
-
- Route::group(['prefix' => 'notes'], function () {
-
- Route::post(
- '/',
- [ Api\NotesController::class,
- 'store'
- ]
- )->name('api.notes.store');
- }); // end notes api routes
-
/**
diff --git a/routes/web.php b/routes/web.php
index b67062e72..14326f160 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -2,6 +2,9 @@
use App\Http\Controllers\Account;
use App\Http\Controllers\ActionlogController;
+use App\Http\Controllers\Auth\ForgotPasswordController;
+use App\Http\Controllers\Auth\LoginController;
+use App\Http\Controllers\Auth\ResetPasswordController;
use App\Http\Controllers\CategoriesController;
use App\Http\Controllers\CompaniesController;
use App\Http\Controllers\DashboardController;
@@ -9,24 +12,22 @@ use App\Http\Controllers\DepartmentsController;
use App\Http\Controllers\DepreciationsController;
use App\Http\Controllers\GroupsController;
use App\Http\Controllers\HealthController;
-use App\Http\Controllers\ImportsController;
use App\Http\Controllers\LabelsController;
use App\Http\Controllers\LocationsController;
use App\Http\Controllers\ManufacturersController;
use App\Http\Controllers\ModalController;
+use App\Http\Controllers\NotesController;
use App\Http\Controllers\ProfileController;
-use App\Http\Controllers\ReportsController;
use App\Http\Controllers\ReportTemplatesController;
+use App\Http\Controllers\ReportsController;
use App\Http\Controllers\SettingsController;
use App\Http\Controllers\StatuslabelsController;
use App\Http\Controllers\SuppliersController;
use App\Http\Controllers\ViewAssetsController;
-use App\Http\Controllers\Auth\LoginController;
-use App\Http\Controllers\Auth\ForgotPasswordController;
-use App\Http\Controllers\Auth\ResetPasswordController;
use App\Livewire\Importer;
+use App\Models\ReportTemplate;
use Illuminate\Support\Facades\Route;
-use Illuminate\Support\Facades\Auth;
+use Tabuna\Breadcrumbs\Trail;
Route::group(['middleware' => 'auth'], function () {
/*
@@ -101,37 +102,27 @@ Route::group(['middleware' => 'auth'], function () {
Route::post('{manufacturers_id}/restore', [ManufacturersController::class, 'restore'] )->name('restore/manufacturer');
});
- Route::resource('manufacturers', ManufacturersController::class, [
- 'parameters' => ['manufacturer' => 'manufacturers_id'],
- ]);
+ Route::resource('manufacturers', ManufacturersController::class);
/*
* Suppliers
*/
- Route::resource('suppliers', SuppliersController::class, [
- 'parameters' => ['supplier' => 'supplier_id'],
- ]);
+ Route::resource('suppliers', SuppliersController::class);
/*
* Depreciations
*/
- Route::resource('depreciations', DepreciationsController::class, [
- 'parameters' => ['depreciation' => 'depreciation_id'],
- ]);
+ Route::resource('depreciations', DepreciationsController::class);
/*
* Status Labels
*/
- Route::resource('statuslabels', StatuslabelsController::class, [
- 'parameters' => ['statuslabel' => 'statuslabel_id'],
- ]);
+ Route::resource('statuslabels', StatuslabelsController::class);
/*
* Departments
*/
- Route::resource('departments', DepartmentsController::class, [
- 'parameters' => ['department' => 'department_id'],
- ]);
+ Route::resource('departments', DepartmentsController::class);
});
/*
@@ -178,46 +169,137 @@ Route::group(['middleware' => 'auth'], function () {
*/
Route::group(['prefix' => 'admin', 'middleware' => ['auth', 'authorize:superuser']], function () {
- Route::get('settings', [SettingsController::class, 'getSettings'])->name('settings.general.index');
- Route::post('settings', [SettingsController::class, 'postSettings'])->name('settings.general.save');
- Route::get('branding', [SettingsController::class, 'getBranding'])->name('settings.branding.index');
- Route::post('branding', [SettingsController::class, 'postBranding'])->name('settings.branding.save');
+ Route::get('settings', [SettingsController::class, 'getSettings'])
+ ->name('settings.general.index')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('settings.index')
+ ->push(trans('admin/settings/general.general_title'), route('settings.general.index')));
- Route::get('security', [SettingsController::class, 'getSecurity'])->name('settings.security.index');
- Route::post('security', [SettingsController::class, 'postSecurity'])->name('settings.security.save');
+ Route::post('settings', [SettingsController::class, 'postSettings'])
+ ->name('settings.general.save');
- Route::get('groups', [GroupsController::class, 'index'])->name('settings.groups.index');
+ Route::get('branding', [SettingsController::class, 'getBranding'])
+ ->name('settings.branding.index')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('settings.index')
+ ->push(trans('admin/settings/general.branding_title'), route('settings.branding.index')));
- Route::get('localization', [SettingsController::class, 'getLocalization'])->name('settings.localization.index');
- Route::post('localization', [SettingsController::class, 'postLocalization'])->name('settings.localization.save');
+ Route::post('branding', [SettingsController::class, 'postBranding'])
+ ->name('settings.branding.save');
- Route::get('notifications', [SettingsController::class, 'getAlerts'])->name('settings.alerts.index');
- Route::post('notifications', [SettingsController::class, 'postAlerts'])->name('settings.alerts.save');
+ Route::get('security', [SettingsController::class, 'getSecurity'])
+ ->name('settings.security.index')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('settings.index')
+ ->push(trans('admin/settings/general.security_title'), route('settings.security.index')));
- Route::get('slack', [SettingsController::class, 'getSlack'])->name('settings.slack.index');
- Route::post('slack', [SettingsController::class, 'postSlack'])->name('settings.slack.save');
+ Route::post('security', [SettingsController::class, 'postSecurity'])
+ ->name('settings.security.save');
- Route::get('asset_tags', [SettingsController::class, 'getAssetTags'])->name('settings.asset_tags.index');
- Route::post('asset_tags', [SettingsController::class, 'postAssetTags'])->name('settings.asset_tags.save');
+ Route::get('localization', [SettingsController::class, 'getLocalization'])
+ ->name('settings.localization.index')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('settings.index')
+ ->push(trans('admin/settings/general.localization_title'), route('settings.localization.index')));
- Route::get('labels', [SettingsController::class, 'getLabels'])->name('settings.labels.index');
- Route::post('labels', [SettingsController::class, 'postLabels'])->name('settings.labels.save');
+ Route::post('localization', [SettingsController::class, 'postLocalization'])
+ ->name('settings.localization.save');
- Route::get('ldap', [SettingsController::class, 'getLdapSettings'])->name('settings.ldap.index');
- Route::post('ldap', [SettingsController::class, 'postLdapSettings'])->name('settings.ldap.save');
+ Route::get('notifications', [SettingsController::class, 'getAlerts'])
+ ->name('settings.alerts.index')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('settings.index')
+ ->push(trans('admin/settings/general.alert_title'), route('settings.alerts.index')));
- Route::get('phpinfo', [SettingsController::class, 'getPhpInfo'])->name('settings.phpinfo.index');
+ Route::post('notifications', [SettingsController::class, 'postAlerts'])
+ ->name('settings.alerts.save');
- Route::get('oauth', [SettingsController::class, 'api'])->name('settings.oauth.index');
+ Route::get('slack', [SettingsController::class, 'getSlack'])
+ ->name('settings.slack.index')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('settings.index')
+ ->push(trans('admin/settings/general.webhook_title'), route('settings.slack.index')));
+
+ Route::post('slack', [SettingsController::class, 'postSlack'])
+ ->name('settings.slack.save');
+
+ Route::get('asset_tags', [SettingsController::class, 'getAssetTags'])
+ ->name('settings.asset_tags.index')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('settings.index')
+ ->push(trans('admin/settings/general.asset_tag_title'), route('settings.asset_tags.index')));
+
+ Route::post('asset_tags', [SettingsController::class, 'postAssetTags'])
+ ->name('settings.asset_tags.save');
+
+ Route::get('labels', [SettingsController::class, 'getLabels'])
+ ->name('settings.labels.index')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('settings.index')
+ ->push(trans('admin/settings/general.labels_title'), route('settings.labels.index')));
+
+ Route::post('labels', [SettingsController::class, 'postLabels'])
+ ->name('settings.labels.save');
+
+ Route::get('ldap', [SettingsController::class, 'getLdapSettings'])
+ ->name('settings.ldap.index')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('settings.index')
+ ->push(trans('admin/settings/general.ldap_ad'), route('settings.ldap.index')));
+
+ Route::post('ldap', [SettingsController::class, 'postLdapSettings'])
+ ->name('settings.ldap.save');
+
+ Route::get('phpinfo', [SettingsController::class, 'getPhpInfo'])
+ ->name('settings.phpinfo.index')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('settings.index')
+ ->push(trans('admin/settings/general.php_info'), route('settings.phpinfo.index')));
+
+ Route::get('oauth', [SettingsController::class, 'api'])
+ ->name('settings.oauth.index')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('settings.index')
+ ->push(trans('admin/settings/general.oauth'), route('settings.oauth.index')));
+
+ Route::get('google', [SettingsController::class, 'getGoogleLoginSettings'])
+ ->name('settings.google.index')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('settings.index')
+ ->push(trans('admin/settings/general.google_login'), route('settings.google.index')));
+
+ Route::post('google', [SettingsController::class, 'postGoogleLoginSettings'])
+ ->name('settings.google.save');
+
+ Route::get('purge', [SettingsController::class, 'getPurge'])
+ ->name('settings.purge.index')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('settings.index')
+ ->push(trans('admin/settings/general.purge'), route('settings.purge.index')));
+
+ Route::post('purge', [SettingsController::class, 'postPurge'])
+ ->name('settings.purge.save');
+
+ Route::get('login-attempts', [SettingsController::class, 'getLoginAttempts'])
+ ->name('settings.logins.index')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('settings.index')
+ ->push(trans('admin/settings/general.login'), route('settings.logins.index')));
+
+
+ // SAML
+ Route::get('/saml', [SettingsController::class, 'getSamlSettings'])
+ ->name('settings.saml.index')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('settings.index')
+ ->push(trans('admin/settings/general.saml_title'), route('settings.saml.index')));
+
+ Route::post('/saml', [SettingsController::class, 'postSamlSettings'])
+ ->name('settings.saml.save');
- Route::get('google', [SettingsController::class, 'getGoogleLoginSettings'])->name('settings.google.index');
- Route::post('google', [SettingsController::class, 'postGoogleLoginSettings'])->name('settings.google.save');
- Route::get('purge', [SettingsController::class, 'getPurge'])->name('settings.purge.index');
- Route::post('purge', [SettingsController::class, 'postPurge'])->name('settings.purge.save');
- Route::get('login-attempts', [SettingsController::class, 'getLoginAttempts'])->name('settings.logins.index');
// Backups
Route::group(['prefix' => 'backups', 'middleware' => 'auth'], function () {
@@ -244,15 +326,25 @@ Route::group(['prefix' => 'admin', 'middleware' => ['auth', 'authorize:superuser
return redirect(route('settings.backups.index'));
});
- Route::get('/', [SettingsController::class, 'getBackups'])->name('settings.backups.index');
+ Route::get('/', [SettingsController::class, 'getBackups'])
+ ->name('settings.backups.index')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('settings.index')
+ ->push(trans('admin/settings/general.backups'), route('settings.backups.index')));
});
- Route::resource('groups', GroupsController::class, [
- 'middleware' => ['auth'],
- 'parameters' => ['group' => 'group_id'],
- ]);
+ Route::resource('groups', GroupsController::class);
- Route::get('/', [SettingsController::class, 'index'])->name('settings.index');
+
+ /**
+ * This breadcrumb is repeated for groups in the BreadcrumbServiceProvider, since groups uses resource routes
+ * and that servcie provider cannot see the breadcrumbs defined below
+ */
+ Route::get('/', [SettingsController::class, 'index'])
+ ->name('settings.index')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('home')
+ ->push(trans('general.admin'), route('settings.index')));
});
/*
@@ -264,9 +356,12 @@ Route::group(['prefix' => 'admin', 'middleware' => ['auth', 'authorize:superuser
|
*/
-Route::get('/import',
- Importer::class
-)->middleware('auth')->name('imports.index');
+Route::get('/import', Importer::class)
+ ->middleware('auth')
+ ->name('imports.index')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('home')
+ ->push(trans('general.import'), route('imports.index')));
/*
|--------------------------------------------------------------------------
@@ -279,44 +374,79 @@ Route::get('/import',
Route::group(['prefix' => 'account', 'middleware' => ['auth']], function () {
// Profile
- Route::get('profile', [ProfileController::class, 'getIndex'])->name('profile');
- Route::post('profile', [ProfileController::class, 'postIndex']);
+ Route::get('profile', [ProfileController::class, 'getIndex'])
+ ->name('profile')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('home')
+ ->push(trans('general.editprofile'), route('profile')));
- Route::get('menu', [ProfileController::class, 'getMenuState'])->name('account.menuprefs');
+ Route::post('profile', [ProfileController::class, 'postIndex'])
+ ->name('profile.update');
- Route::get('password', [ProfileController::class, 'password'])->name('account.password.index');
- Route::post('password', [ProfileController::class, 'passwordSave']);
+ Route::get('menu', [ProfileController::class, 'getMenuState'])
+ ->name('account.menuprefs');
- Route::get('api', [ProfileController::class, 'api'])->name('user.api');
+ Route::get('password', [ProfileController::class, 'password'])
+ ->name('account.password.index')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('home')
+ ->push(trans('general.changepassword'), route('account.password.index')));
+
+ Route::post('password', [ProfileController::class, 'passwordSave'])
+ ->name('account.password.update');
+
+ Route::get('api', [ProfileController::class, 'api'])
+ ->name('user.api')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('home')
+ ->push(trans('general.manage_api_keys'), route('user.api')));
// View Assets
- Route::get('view-assets', [ViewAssetsController::class, 'getIndex'])->name('view-assets');
+ Route::get('view-assets', [ViewAssetsController::class, 'getIndex'])
+ ->name('view-assets')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('home')
+ ->push(trans('general.viewassets'), route('view-assets')));
- Route::get('requested', [ViewAssetsController::class, 'getRequestedAssets'])->name('account.requested');
+ Route::get('requested', [ViewAssetsController::class, 'getRequestedAssets'])
+ ->name('account.requested')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('home')
+ ->push(trans('general.requested_assets_menu'), route('account.requested')));
// Profile
Route::get(
- 'requestable-assets',
- [ViewAssetsController::class, 'getRequestableIndex']
- )->name('requestable-assets');
- Route::post(
- 'request-asset/{assetId}',
- [ViewAssetsController::class, 'getRequestAsset']
- )->name('account/request-asset');
+ 'requestable-assets', [ViewAssetsController::class, 'getRequestableIndex'])
+ ->name('requestable-assets')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('home')
+ ->push(trans('general.requestable_items'), route('requestable-assets')));
- Route::post(
- 'request/{itemType}/{itemId}/{cancel_by_admin?}/{requestingUser?}',
- [ViewAssetsController::class, 'getRequestItem']
- )->name('account/request-item');
+
+ Route::post('request-asset/{asset}', [ViewAssetsController::class, 'store'])
+ ->name('account.request-asset');
+
+ Route::post('request-asset/{asset}/cancel', [ViewAssetsController::class, 'destroy'])
+ ->name('account.request-asset.cancel');
+
+ Route::post('request/{itemType}/{itemId}/{cancel_by_admin?}/{requestingUser?}', [ViewAssetsController::class, 'getRequestItem'])
+ ->name('account/request-item');
// Account Dashboard
- Route::get('/', [ViewAssetsController::class, 'getIndex'])->name('account');
+ Route::get('/', [ViewAssetsController::class, 'getIndex'])
+ ->name('account');
Route::get('accept', [Account\AcceptanceController::class, 'index'])
- ->name('account.accept');
+ ->name('account.accept')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('home')
+ ->push(trans('general.accept_assets_menu'), route('account.accept')));
Route::get('accept/{id}', [Account\AcceptanceController::class, 'create'])
- ->name('account.accept.item');
+ ->name('account.accept.item')
+ ->breadcrumbs(fn (Trail $trail, $id) =>
+ $trail->parent('account.accept')
+ ->push(trans('general.accept_item'), route('account.accept.item', $id)));
Route::post('accept/{id}', [Account\AcceptanceController::class, 'store'])
->name('account.store-acceptance');
@@ -340,76 +470,132 @@ Route::group(['prefix' => 'account', 'middleware' => ['auth']], function () {
});
Route::group(['middleware' => ['auth']], function () {
- Route::get('reports/audit',
- [ReportsController::class, 'audit']
- )->name('reports.audit');
+ Route::post('notes', [NotesController::class, 'store'])->name('notes.store');
+});
+
+Route::group(['prefix' => 'reports', 'middleware' => ['auth']], function () {
+
+ Route::get('audit', [ReportsController::class, 'audit'])
+ ->name('reports.audit')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('home')
+ ->push(trans('general.audit_report'), route('reports.audit')));
Route::get(
- 'reports/depreciation',
- [ReportsController::class, 'getDeprecationReport']
- )->name('reports/depreciation');
- Route::get(
- 'reports/export/depreciation',
- [ReportsController::class, 'exportDeprecationReport']
- )->name('reports/export/depreciation');
- Route::get(
- 'reports/asset_maintenances',
- [ReportsController::class, 'getAssetMaintenancesReport']
- )->name('reports/asset_maintenances');
- Route::get(
- 'reports/export/asset_maintenances',
- [ReportsController::class, 'exportAssetMaintenancesReport']
- )->name('reports/export/asset_maintenances');
- Route::get(
- 'reports/licenses',
- [ReportsController::class, 'getLicenseReport']
- )->name('reports/licenses');
- Route::get(
- 'reports/export/licenses',
- [ReportsController::class, 'exportLicenseReport']
- )->name('reports/export/licenses');
+ 'depreciation', [ReportsController::class, 'getDeprecationReport'])
+ ->name('reports/depreciation')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('home')
+ ->push(trans('general.depreciation_report'), route('reports/depreciation')));
- Route::get('reports/accessories', [ReportsController::class, 'getAccessoryReport'])->name('reports/accessories');
- Route::get(
- 'reports/export/accessories',
- [ReportsController::class, 'exportAccessoryReport']
- )->name('reports/export/accessories');
- Route::get('reports/custom', [ReportsController::class, 'getCustomReport'])->name('reports/custom');
- Route::post('reports/custom', [ReportsController::class, 'postCustom']);
- Route::prefix('reports/templates')->name('report-templates')->group(function () {
- Route::post('/', [ReportTemplatesController::class, 'store'])->name('.store');
- Route::get('/{reportTemplate}', [ReportTemplatesController::class, 'show'])->name('.show');
- Route::get('/{reportTemplate}/edit', [ReportTemplatesController::class, 'edit'])->name('.edit');
- Route::post('/{reportTemplate}', [ReportTemplatesController::class, 'update'])->name('.update');
- Route::delete('/{reportTemplate}', [ReportTemplatesController::class, 'destroy'])->name('.destroy');
+ // Is this still used??
+ Route::get(
+ 'export/depreciation', [ReportsController::class, 'exportDeprecationReport'])
+ ->name('reports/export/depreciation')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('home')
+ ->push(trans('general.depreciation_report'), route('reports.audit')));
+
+ Route::get(
+ 'asset_maintenances', [ReportsController::class, 'getAssetMaintenancesReport'])
+ ->name('reports/asset_maintenances')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('home')
+ ->push(trans('general.asset_maintenance_report'), route('reports/asset_maintenances')));
+
+ // Is this still used?
+ Route::get('export/asset_maintenances', [ReportsController::class, 'exportAssetMaintenancesReport'])
+ ->name('reports/export/asset_maintenances')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('home')
+ ->push(trans('general.asset_maintenance_report'), route('reports/export/asset_maintenances')));
+
+ Route::get('licenses', [ReportsController::class, 'getLicenseReport'])
+ ->name('reports/licenses')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('home')
+ ->push(trans('general.license_report'), route('reports/licenses')));
+
+ Route::get('export/licenses', [ReportsController::class, 'exportLicenseReport'])
+ ->name('reports/export/licenses');
+
+ Route::get('accessories', [ReportsController::class, 'getAccessoryReport'])
+ ->name('reports/accessories');
+
+ Route::get('export/accessories', [ReportsController::class, 'exportAccessoryReport'])
+ ->name('reports/export/accessories');
+
+ Route::get('custom', [ReportsController::class, 'getCustomReport'])
+ ->name('reports/custom')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('home')
+ ->push(trans('general.custom_report'), route('reports/custom')));
+
+ Route::post('custom', [ReportsController::class, 'postCustom'])
+ ->name('reports.post-custom');
+
+
+ Route::prefix('templates')
+ ->group(function () {
+
+ Route::post('/', [ReportTemplatesController::class, 'store'])
+ ->name('report-templates.store');
+
+ // The breadcrumb on this is a little odd for now since we don't have a template index
+ Route::get('/{reportTemplate}', [ReportTemplatesController::class, 'show'])
+ ->name('report-templates.show')
+ ->breadcrumbs(fn (Trail $trail, ReportTemplate $reportTemplate) =>
+ $trail->parent('reports/custom')
+ ->push($reportTemplate->name, null)
+ ->push(trans('general.customize_report'), ''));
+
+ Route::get('/{reportTemplate}/edit', [ReportTemplatesController::class, 'edit'])
+ ->name('report-templates.edit')
+ ->breadcrumbs(fn (Trail $trail, ReportTemplate $reportTemplate) =>
+ $trail->parent('reports/custom')
+ ->push($reportTemplate->name, route('report-templates.show', $reportTemplate))
+ ->push(trans('general.customize_report'), ''));
+
+
+ Route::post('/{reportTemplate}', [ReportTemplatesController::class, 'update'])
+ ->name('report-templates.update');
+
+ Route::delete('/{reportTemplate}', [ReportTemplatesController::class, 'destroy'])
+ ->name('report-templates.destroy');
});
- Route::get(
- 'reports/activity',
- [ReportsController::class, 'getActivityReport']
- )->name('reports.activity');
- Route::post('reports/activity', [ReportsController::class, 'postActivityReport']);
Route::get(
- 'reports/unaccepted_assets/{deleted?}',
- [ReportsController::class, 'getAssetAcceptanceReport']
- )->name('reports/unaccepted_assets');
+ 'activity', [ReportsController::class, 'getActivityReport'])
+ ->name('reports.activity')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('home')
+ ->push(trans('general.activity_report'), route('reports.activity')));
+
+ Route::post('activity', [ReportsController::class, 'postActivityReport'])
+ ->name('reports.activity.post');
+
+ Route::get('unaccepted_assets/{deleted?}', [ReportsController::class, 'getAssetAcceptanceReport'])
+ ->name('reports/unaccepted_assets')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('home')
+ ->push(trans('general.unaccepted_asset_report'), route('reports/unaccepted_assets')));
+
+ Route::post('unaccepted_assets/sent_reminder', [ReportsController::class, 'sentAssetAcceptanceReminder'])
+ ->name('reports/unaccepted_assets_sent_reminder');
+
+ Route::delete('unaccepted_assets/{acceptanceId}/delete', [ReportsController::class, 'deleteAssetAcceptance'])
+ ->name('reports/unaccepted_assets_delete');
+
Route::post(
- 'reports/unaccepted_assets/sent_reminder',
- [ReportsController::class, 'sentAssetAcceptanceReminder']
- )->name('reports/unaccepted_assets_sent_reminder');
- Route::delete(
- 'reports/unaccepted_assets/{acceptanceId}/delete',
- [ReportsController::class, 'deleteAssetAcceptance']
- )->name('reports/unaccepted_assets_delete');
- Route::post(
- 'reports/unaccepted_assets/{deleted?}',
- [ReportsController::class, 'postAssetAcceptanceReport']
- )->name('reports/export/unaccepted_assets');
+ 'unaccepted_assets/{deleted?}', [ReportsController::class, 'postAssetAcceptanceReport'])
+ ->name('reports/export/unaccepted_assets');
+
});
+
Route::get(
'auth/signin',
[LoginController::class, 'legacyAuthRedirect']
@@ -521,14 +707,6 @@ Route::group(['middleware' => 'web'], function () {
Route::get('google/callback', 'App\Http\Controllers\GoogleAuthController@handleGoogleCallback')->name('google.callback');
- Route::get(
- '/',
- [
- 'as' => 'home',
- 'middleware' => ['auth'],
- 'uses' => 'DashboardController@getIndex' ]
- );
-
// need to keep GET /logout for SAML SLO
Route::get(
'logout',
@@ -554,4 +732,7 @@ Route::withoutMiddleware(['web'])->get(
Route::middleware(['auth'])->get(
'/',
[DashboardController::class, 'index']
-)->name('home');
+)->name('home')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->push('Home', route('home'))
+ );
diff --git a/routes/web/accessories.php b/routes/web/accessories.php
index 1f28892a0..3955db71b 100644
--- a/routes/web/accessories.php
+++ b/routes/web/accessories.php
@@ -42,7 +42,7 @@ Route::group(['prefix' => 'accessories', 'middleware' => ['auth']], function ()
[Accessories\AccessoriesFilesController::class, 'show']
)->name('show.accessoryfile');
- Route::get('{accessoryId}/clone',
+ Route::get('{accessory}/clone',
[Accessories\AccessoriesController::class, 'getClone']
)->name('clone/accessories');
@@ -53,6 +53,5 @@ Route::group(['prefix' => 'accessories', 'middleware' => ['auth']], function ()
});
Route::resource('accessories', Accessories\AccessoriesController::class, [
- 'middleware' => ['auth'],
- 'parameters' => ['accessory' => 'accessory_id'],
+ 'middleware' => ['auth']
]);
diff --git a/routes/web/fields.php b/routes/web/fields.php
index cdd8fda5d..7500053c7 100644
--- a/routes/web/fields.php
+++ b/routes/web/fields.php
@@ -33,14 +33,17 @@ Route::group([ 'prefix' => 'fields','middleware' => ['auth'] ], function () {
)->name('fieldsets.associate');
Route::resource('fieldsets', CustomFieldsetsController::class, [
- 'parameters' => ['fieldset' => 'field_id', 'field' => 'field_id']
+ 'parameters' => [
+ 'fieldset' => 'fieldset',
+ 'field' => 'field_id'
+ ]
]);
});
-Route::resource('fields', CustomFieldsController::class, [
- 'middleware' => ['auth'],
- 'parameters' => ['field' => 'field_id', 'fieldset' => 'fieldset_id'],
-]);
+Route::resource('fields', CustomFieldsController::class,
+ ['middleware' => ['auth'],
+ 'except' => ['show']
+ ]);
diff --git a/routes/web/hardware.php b/routes/web/hardware.php
index ee888aa1d..21f4f40d4 100644
--- a/routes/web/hardware.php
+++ b/routes/web/hardware.php
@@ -6,7 +6,10 @@ use App\Http\Controllers\Assets\BulkAssetsController;
use App\Http\Controllers\Assets\AssetCheckoutController;
use App\Http\Controllers\Assets\AssetCheckinController;
use App\Http\Controllers\Assets\AssetFilesController;
+use App\Models\Setting;
+use Tabuna\Breadcrumbs\Trail;
use Illuminate\Support\Facades\Route;
+use App\Models\Asset;
/*
|--------------------------------------------------------------------------
@@ -24,47 +27,59 @@ Route::group(
function () {
- Route::get('bulkaudit',
- [AssetsController::class, 'quickScan']
- )->name('assets.bulkaudit');
+ Route::get('bulkaudit', [AssetsController::class, 'quickScan'])
+ ->name('assets.bulkaudit')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('hardware.index')
+ ->push(trans('general.bulkaudit'), route('asset.import-history'))
+ );
- Route::get('quickscancheckin',
- [AssetsController::class, 'quickScanCheckin']
- )->name('hardware/quickscancheckin');
+ Route::get('quickscancheckin', [AssetsController::class, 'quickScanCheckin'])
+ ->name('hardware/quickscancheckin')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('hardware.index')
+ ->push('Quickscan Checkin', route('hardware/quickscancheckin'))
+ );
- // Asset Maintenances
- Route::resource('maintenances',
- AssetMaintenancesController::class, [
- 'parameters' => ['maintenance' => 'maintenance_id', 'asset' => 'asset_id'],
- ]);
+ Route::get('requested', [AssetsController::class, 'getRequestedIndex'])
+ ->name('assets.requested')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('hardware.index')
+ ->push(trans('admin/hardware/general.requested'), route('assets.requested'))
+ );
- Route::get('requested', [
- AssetsController::class, 'getRequestedIndex']
- )->name('assets.requested');
-
- Route::get('scan',
- [AssetsController::class, 'scan']
- )->name('asset.scan');
-
- Route::get('audit/due',
- [AssetsController::class, 'dueForAudit']
- )->name('assets.audit.due');
+ Route::get('audit/due', [AssetsController::class, 'dueForAudit'])
+ ->name('assets.audit.due')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('hardware.index')
+ ->push(trans_choice('general.audit_due_days', Setting::getSettings()->audit_warning_days, ['days' => Setting::getSettings()->audit_warning_days]), route('assets.audit.due'))
+ );
Route::get('checkins/due',
[AssetsController::class, 'dueForCheckin']
- )->name('assets.checkins.due');
+ )->name('assets.checkins.due')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('hardware.index')
+ ->push(trans_choice('general.checkin_due_days', Setting::getSettings()->due_checkin_days, ['days' => Setting::getSettings()->due_checkin_days]), route('assets.audit.due'))
+ );
- Route::get('audit/{id}',
- [AssetsController::class, 'audit']
- )->name('asset.audit.create');
+ Route::get('{asset}/audit', [AssetsController::class, 'audit'])
+ ->name('asset.audit.create')
+ ->breadcrumbs(fn (Trail $trail, Asset $asset) =>
+ $trail->parent('hardware.show', $asset)
+ ->push(trans('general.audit'))
+ );
- Route::post('audit/{id}',
+ Route::post('{asset}/audit',
[AssetsController::class, 'auditStore']
)->name('asset.audit.store');
- Route::get('history',
- [AssetsController::class, 'getImportHistory']
- )->name('asset.import-history');
+ Route::get('history', [AssetsController::class, 'getImportHistory'])
+ ->name('asset.import-history')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('hardware.index')
+ ->push(trans('general.import-history'), route('asset.import-history'))
+ );
Route::post('history',
[AssetsController::class, 'postImportHistory']
@@ -85,19 +100,25 @@ Route::group(
Route::get('{assetId}/label',
[AssetsController::class, 'getLabel']
)->name('label/hardware');
-
- Route::get('{assetId}/checkout',
- [AssetCheckoutController::class, 'create']
- )->name('hardware.checkout.create');
+ Route::get('{asset}/checkout', [AssetCheckoutController::class, 'create'])
+ ->name('hardware.checkout.create')
+ ->breadcrumbs(fn (Trail $trail, Asset $asset) =>
+ $trail->parent('hardware.show', $asset)
+ ->push(trans('admin/hardware/general.checkout'), route('hardware.index'))
+ );
Route::post('{assetId}/checkout',
[AssetCheckoutController::class, 'store']
)->name('hardware.checkout.store');
- Route::get('{assetId}/checkin/{backto?}',
+ Route::get('{asset}/checkin/{backto?}',
[AssetCheckinController::class, 'create']
- )->name('hardware.checkin.create');
+ )->name('hardware.checkin.create')
+ ->breadcrumbs(fn (Trail $trail, Asset $asset) =>
+ $trail->parent('hardware.show', $asset)
+ ->push(trans('admin/hardware/general.checkin'), route('hardware.index'))
+ );
Route::post('{assetId}/checkin/{backto?}',
[AssetCheckinController::class, 'store']
@@ -105,32 +126,32 @@ Route::group(
// Redirect old legacy /asset_id/view urls to the resource route version
Route::get('{assetId}/view', function ($assetId) {
- return redirect()->route('hardware.show', ['hardware' => $assetId]);
+ return redirect()->route('hardware.show', $assetId);
});
- Route::get('{assetId}/qr_code',
+ Route::get('{asset}/qr_code',
[AssetsController::class, 'getQrCode']
- )->name('qr_code/hardware');
+ )->name('qr_code/hardware')->withTrashed();
- Route::get('{assetId}/barcode',
+ Route::get('{asset}/barcode',
[AssetsController::class, 'getBarCode']
- )->name('barcode/hardware');
+ )->name('barcode/hardware')->withTrashed();
- Route::post('{assetId}/restore',
+ Route::post('{asset}/restore',
[AssetsController::class, 'getRestore']
- )->name('restore/hardware');
+ )->name('restore/hardware')->withTrashed();
- Route::post('{assetId}/upload',
+ Route::post('{asset}/upload',
[AssetFilesController::class, 'store']
- )->name('upload/asset');
+ )->name('upload/asset')->withTrashed();
- Route::get('{assetId}/showfile/{fileId}/{download?}',
+ Route::get('{asset}/showfile/{fileId}/{download?}',
[AssetFilesController::class, 'show']
- )->name('show/assetfile');
+ )->name('show/assetfile')->withTrashed();
- Route::delete('{assetId}/showfile/{fileId}/delete',
+ Route::delete('{asset}/showfile/{fileId}/delete',
[AssetFilesController::class, 'destroy']
- )->name('delete/assetfile');
+ )->name('delete/assetfile')->withTrashed();
Route::post(
'bulkedit',
@@ -153,9 +174,12 @@ Route::group(
)->name('hardware/bulksave');
// Bulk checkout / checkin
- Route::get('bulkcheckout',
- [BulkAssetsController::class, 'showCheckout']
- )->name('hardware.bulkcheckout.show');
+ Route::get('bulkcheckout', [BulkAssetsController::class, 'showCheckout'])
+ ->name('hardware.bulkcheckout.show')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('hardware.index')
+ ->push(trans('admin/hardware/general.bulk_checkout'), route('hardware.index'))
+ );
Route::post('bulkcheckout',
[BulkAssetsController::class, 'storeCheckout']
@@ -163,16 +187,17 @@ Route::group(
});
-Route::resource('hardware',
- AssetsController::class,
- [
- 'middleware' => ['auth'],
- 'parameters' => ['asset' => 'asset_id',
- 'names' => [
- 'show' => 'view',
- ],
- ],
-]);
+Route::resource('hardware',
+ AssetsController::class,
+ ['middleware' => ['auth']
+])->parameters(['hardware' => 'asset'])->withTrashed();
+
+
+// Asset Maintenances
+Route::resource('maintenances',
+ AssetMaintenancesController::class, [
+ 'parameters' => ['maintenance' => 'maintenance', 'asset' => 'asset_id'],
+ ]);
Route::get('ht/{any?}',
[AssetsController::class, 'getAssetByTag']
diff --git a/routes/web/kits.php b/routes/web/kits.php
index 6b687f0bf..abd2d7526 100644
--- a/routes/web/kits.php
+++ b/routes/web/kits.php
@@ -1,21 +1,17 @@
['auth'],
- 'parameters' => ['kit' => 'kit_id'],
-]);
-
-Route::group(['prefix' => 'kits/{kit_id}', 'middleware' => ['auth']], function () {
+Route::group(['prefix' => 'kits/{kit}', 'middleware' => ['auth']], function () {
// Route::get('licenses',
// [Kits\PredefinedKitsController::class, 'indexLicenses']
// )->name('kits.licenses.index');
- Route::post('licenses',
+ Route::put('licenses',
[Kits\PredefinedKitsController::class, 'storeLicense']
)->name('kits.licenses.store');
@@ -23,9 +19,11 @@ Route::group(['prefix' => 'kits/{kit_id}', 'middleware' => ['auth']], function (
[Kits\PredefinedKitsController::class, 'updateLicense']
)->name('kits.licenses.update');
- Route::get('licenses/{license_id}/edit',
- [Kits\PredefinedKitsController::class, 'editLicense']
- )->name('kits.licenses.edit');
+ Route::get('licenses/{license_id}/edit', [Kits\PredefinedKitsController::class, 'editLicense'])
+ ->name('kits.licenses.edit')
+ ->breadcrumbs(fn (Trail $trail) =>
+ $trail->parent('settings.index')
+ ->push(trans('admin/settings/general.backups'), route('kits.licenses.edit')));
Route::delete('licenses/{license_id}',
[Kits\PredefinedKitsController::class, 'detachLicense']
@@ -35,7 +33,7 @@ Route::group(['prefix' => 'kits/{kit_id}', 'middleware' => ['auth']], function (
Route::put('models/{model_id}',
[Kits\PredefinedKitsController::class, 'updateModel']
- )/* ->parameters([2 => 'kit_id', 1 => 'model_id'])*/->name('kits.models.update');
+ )->name('kits.models.update');
Route::get('models/{model_id}/edit',
[Kits\PredefinedKitsController::class, 'editModel']
@@ -63,18 +61,24 @@ Route::group(['prefix' => 'kits/{kit_id}', 'middleware' => ['auth']], function (
[Kits\PredefinedKitsController::class, 'updateAccessory']
)/*->parameters([2 => 'kit_id', 1 => 'accessory_id'])*/->name('kits.accessories.update');
- Route::get('accessories/{accessory_id}/edit',
- [Kits\PredefinedKitsController::class, 'editAccessory']
- )->name('kits.accessories.edit');
+ Route::get('accessories/{accessory_id}/edit', [Kits\PredefinedKitsController::class, 'editAccessory'])
+ ->name('kits.accessories.edit');
- Route::delete('accessories/{accessory_id}',
- [Kits\PredefinedKitsController::class, 'detachAccessory']
- )->name('kits.accessories.detach');
- Route::get('checkout',
- [Kits\CheckoutKitController::class, 'showCheckout']
- )->name('kits.checkout.show');
+ Route::delete('accessories/{accessory_id}', [Kits\PredefinedKitsController::class, 'detachAccessory'])
+ ->name('kits.accessories.detach');
- Route::post('checkout',
- [Kits\CheckoutKitController::class, 'store']
- )->name('kits.checkout.store');
+ Route::get('checkout', [Kits\CheckoutKitController::class, 'showCheckout'])
+ ->name('kits.checkout.show')
+ ->breadcrumbs(fn (Trail $trail, PredefinedKit $kit) =>
+ $trail->parent('kits.show', $kit)
+ ->push(trans('general.checkout'), route('kits.checkout.show', $kit)));
+
+ Route::post('checkout', [Kits\CheckoutKitController::class, 'store'])
+ ->name('kits.checkout.store');
}); // kits
+
+// Predefined Kit Management
+Route::resource('kits', Kits\PredefinedKitsController::class, [
+ 'middleware' => ['auth'],
+]);
+
diff --git a/routes/web/licenses.php b/routes/web/licenses.php
index 7212a4764..39762c95c 100644
--- a/routes/web/licenses.php
+++ b/routes/web/licenses.php
@@ -2,6 +2,9 @@
use App\Http\Controllers\Licenses;
use Illuminate\Support\Facades\Route;
+use App\Models\License;
+use App\Models\LicenseSeat;
+use Tabuna\Breadcrumbs\Trail;
// Licenses
Route::group(['prefix' => 'licenses', 'middleware' => ['auth']], function () {
@@ -10,16 +13,25 @@ Route::group(['prefix' => 'licenses', 'middleware' => ['auth']], function () {
Route::get('{licenseId}/freecheckout',
[Licenses\LicensesController::class, 'getFreeLicense']
)->name('licenses.freecheckout');
- Route::get('{licenseId}/checkout/{seatId?}',
- [Licenses\LicenseCheckoutController::class, 'create']
- )->name('licenses.checkout');
+
+ Route::get('{license}/checkout/{seatId?}', [Licenses\LicenseCheckoutController::class, 'create'])
+ ->name('licenses.checkout')
+ ->breadcrumbs(fn (Trail $trail, License $license) =>
+ $trail->parent('licenses.show', $license)
+ ->push(trans('general.checkout'), route('licenses.checkout', $license))
+ );
+
Route::post(
'{licenseId}/checkout/{seatId?}',
[Licenses\LicenseCheckoutController::class, 'store']
); //name() would duplicate here, so we skip it.
- Route::get('{licenseSeatId}/checkin/{backto?}',
- [Licenses\LicenseCheckinController::class, 'create']
- )->name('licenses.checkin');
+
+ Route::get('{licenseSeat}/checkin/{backto?}', [Licenses\LicenseCheckinController::class, 'create'])
+ ->name('licenses.checkin')
+ ->breadcrumbs(fn (Trail $trail, LicenseSeat $licenseSeat) =>
+ $trail->parent('licenses.show', $licenseSeat->license)
+ ->push(trans('general.checkin'), route('licenses.checkin', $licenseSeat))
+ );
Route::post('{licenseId}/checkin/{backto?}',
[Licenses\LicenseCheckinController::class, 'store']
@@ -59,5 +71,4 @@ Route::group(['prefix' => 'licenses', 'middleware' => ['auth']], function () {
Route::resource('licenses', Licenses\LicensesController::class, [
'middleware' => ['auth'],
- 'parameters' => ['license' => 'license_id'],
]);
diff --git a/routes/web/models.php b/routes/web/models.php
index 37e54995b..2325846ea 100644
--- a/routes/web/models.php
+++ b/routes/web/models.php
@@ -10,33 +10,33 @@ use Illuminate\Support\Facades\Route;
Route::group(['prefix' => 'models', 'middleware' => ['auth']], function () {
- Route::post('{modelID}/upload',
+ Route::post('{model}/upload',
[AssetModelsFilesController::class, 'store']
- )->name('upload/models');
+ )->name('upload/models')->withTrashed();
- Route::get('{modelID}/showfile/{fileId}/{download?}',
+ Route::get('{model}/showfile/{fileId}/{download?}',
[AssetModelsFilesController::class, 'show']
- )->name('show/modelfile');
+ )->name('show/modelfile')->withTrashed();
- Route::delete('{modelID}/showfile/{fileId}/delete',
+ Route::delete('{model}/showfile/{fileId}/delete',
[AssetModelsFilesController::class, 'destroy']
- )->name('delete/modelfile');
+ )->name('delete/modelfile')->withTrashed();
Route::get(
- '{modelId}/clone',
+ '{model}/clone',
[
AssetModelsController::class,
'getClone'
]
- )->name('models.clone.create');
+ )->name('models.clone.create')->withTrashed();
Route::post(
- '{modelId}/clone',
+ '{model}/clone',
[
AssetModelsController::class,
'postCreate'
]
- )->name('models.clone.store');
+ )->name('models.clone.store')->withTrashed();
Route::get(
'{modelId}/view',
@@ -92,5 +92,4 @@ Route::group(['prefix' => 'models', 'middleware' => ['auth']], function () {
Route::resource('models', AssetModelsController::class, [
'middleware' => ['auth'],
- 'parameters' => ['model' => 'model_id'],
-]);
+])->withTrashed();
diff --git a/routes/web/users.php b/routes/web/users.php
index e6aaf644a..ced9c379a 100644
--- a/routes/web/users.php
+++ b/routes/web/users.php
@@ -33,20 +33,20 @@ Route::group(['prefix' => 'users', 'middleware' => ['auth']], function () {
)->name('users.export');
Route::get(
- '{userId}/clone',
+ '{user}/clone',
[
Users\UsersController::class,
'getClone'
]
- )->name('users.clone.show');
+ )->name('users.clone.show')->withTrashed();
Route::post(
- '{userId}/clone',
+ '{user}/clone',
[
Users\UsersController::class,
'postCreate'
]
- )->name('users.clone.store');
+ )->name('users.clone.store')->withTrashed();
Route::post(
'{userId}/restore',
@@ -65,12 +65,12 @@ Route::group(['prefix' => 'users', 'middleware' => ['auth']], function () {
)->name('unsuspend/user');
Route::post(
- '{userId}/upload',
+ '{user}/upload',
[
Users\UserFilesController::class,
'store'
]
- )->name('upload/user');
+ )->name('upload/user')->withTrashed();
Route::delete(
'{userId}/deletefile/{fileId}',
@@ -81,12 +81,12 @@ Route::group(['prefix' => 'users', 'middleware' => ['auth']], function () {
)->name('userfile.destroy');
Route::get(
- '{userId}/showfile/{fileId}',
+ '{user}/showfile/{fileId}',
[
Users\UserFilesController::class,
'show'
]
- )->name('show/userfile');
+ )->name('show/userfile')->withTrashed();
Route::post(
'{userId}/password',
@@ -145,12 +145,8 @@ Route::group(['prefix' => 'users', 'middleware' => ['auth']], function () {
]
)->name('users/bulkeditsave');
- // pulling this out of the resource because I need route model binding in the request
- Route::match(['put', 'patch'], '/{user}', [Users\UsersController::class, 'update'])->name('users.update');
});
Route::resource('users', Users\UsersController::class, [
- 'middleware' => ['auth'],
- 'parameters' => ['user' => 'user_id'],
- 'except' => ['update']
-]);
+ 'middleware' => ['auth']
+])->withTrashed();
diff --git a/sample_csvs/accessories-sample.csv b/sample_csvs/accessories-sample.csv
index 6c203ec5a..d884cc0cf 100644
--- a/sample_csvs/accessories-sample.csv
+++ b/sample_csvs/accessories-sample.csv
@@ -1,51 +1,4 @@
Company,Name,Category,Supplier,Manufacturer,QTY,Location,Order Number,Min Amount,Model Number,Notes,Purchase Date,Purchase Cost
-Kovacek and Sons,Dragline,Roofing (Metal),Runte-Corwin,Cremin Inc,61,Artashat,017019279-2,59,3558344370409403,sit amet diam in magna bibendum imperdiet nullam orci pede venenatis non sodales sed tincidunt eu felis,2022-08-24,73.96
-"Russel, Marquardt and Frami",Skid-Steer,Drywall & Acoustical (MOB),Bins-Gerhold,Breitenberg Inc,17,Dugcal,801930572-6,10,30104302249895,diam cras pellentesque volutpat dui maecenas tristique est et tempus semper est quam pharetra magna ac,2022-06-24,5.27
-Nicolas Group,Dragline,Structural & Misc Steel Erection,"Glover, Kovacek and Bechtelar",Kulas-Powlowski,36,Zafarwāl,963844173-9,30,3577672351043826,vulputate ut ultrices vel augue vestibulum ante ipsum primis in,2023-03-16,87.89
-Roob-Mante,Dump Truck,Electrical and Fire Alarm,"Rath, Mueller and Halvorson","Bogisich, Schaefer and Goldner",14,Askiz,778878918-6,58,3578492711349816,donec ut mauris eget massa tempor convallis nulla neque libero convallis eget eleifend luctus,2023-03-30,57.51
-"Marquardt, Blanda and Heaney",Crawler,RF Shielding,Bode-Armstrong,Rosenbaum LLC,54,Arjona,414053159-2,82,201552275235023,augue luctus tincidunt nulla mollis molestie lorem quisque ut erat curabitur gravida nisi at nibh,2022-06-27,76.18
-Herman and Sons,Skid-Steer,"Doors, Frames & Hardware","Volkman, Pagac and Tillman",McKenzie-Hand,12,Višnjevac,064912598-3,29,3530080559523225,dolor morbi vel lectus in quam fringilla rhoncus mauris enim leo rhoncus sed vestibulum sit amet cursus id turpis,2022-08-14,97.9
-Morar Inc,Crawler,Exterior Signage,Bergstrom-Ullrich,"Mann, Ondricka and Fadel",69,Birni N Konni,770515508-7,11,3529943453252733,sapien in sapien iaculis congue vivamus metus arcu adipiscing molestie,2022-10-28,73.62
-Jerde-Bogisich,Compactor,Drilled Shafts,Fadel-Sporer,"Bode, Gutkowski and Schamberger",58,Galižana,188679996-2,95,4041376614376,gravida nisi at nibh in hac habitasse platea dictumst aliquam augue quam sollicitudin,2023-03-09,3.83
-"Dibbert, Roberts and Orn",Dragline,EIFS,Waters LLC,"Pollich, Treutel and Homenick",93,Tongyangdao,303831002-6,53,5602237646576545,donec ut mauris eget massa tempor convallis nulla neque libero convallis eget eleifend luctus,2022-12-07,67.91
-Schmidt-Jones,Compactor,Site Furnishings,"Monahan, Dooley and Rowe",Gorczany and Sons,5,Chunghwa,054966680-X,8,3548920584271979,enim sit amet nunc viverra dapibus nulla suscipit ligula in lacus curabitur at,2022-08-23,91.79
-"Lemke, Nicolas and Jaskolski",Trencher,Drilled Shafts,Nienow-Moen,Kuhn-Sawayn,40,Mkushi,921741957-5,94,3566485930049161,arcu libero rutrum ac lobortis vel dapibus at diam nam tristique,2022-09-16,37.6
-Hills-Sipes,Compactor,Roofing (Asphalt),"Kulas, Bogisich and Mante","Grimes, Ziemann and Jacobs",99,Orahovica,794640267-8,69,490344354231467621,eu nibh quisque id justo sit amet sapien dignissim vestibulum,2023-02-11,91.86
-Block and Sons,Bulldozer,Prefabricated Aluminum Metal Canopies,Jakubowski LLC,Feest-King,32,Al Jubayhah,459697074-2,27,5010129157541263,nec sem duis aliquam convallis nunc proin at turpis a pede posuere nonummy integer non velit donec diam,2022-11-17,82.9
-Denesik-Champlin,Compactor,Retaining Wall and Brick Pavers,"Barrows, Bradtke and Kertzmann","Ledner, Hodkiewicz and McLaughlin",28,Talalayivka,301762319-X,81,4575609485591,nullam orci pede venenatis non sodales sed tincidunt eu felis fusce posuere felis sed lacus morbi sem,2023-04-20,78.21
-Towne Inc,Trencher,Rebar & Wire Mesh Install,Runolfsdottir-Klein,"Hilll, Herman and Roberts",76,Cunén,739224317-9,56,3529871501469720,luctus rutrum nulla tellus in sagittis dui vel nisl duis ac nibh fusce lacus,2022-08-31,87.49
-Cummerata LLC,Dragline,Fire Sprinkler System,O'Kon-Conn,"Schmeler, Reichel and Jakubowski",11,Boju,674114839-6,61,3580499860326855,lacus curabitur at ipsum ac tellus semper interdum mauris ullamcorper purus sit amet nulla quisque arcu libero rutrum ac,2022-10-15,56.48
-Brekke LLC,Backhoe,"Temp Fencing, Decorative Fencing and Gates",Fisher LLC,Graham-Kshlerin,83,Lolak,496879420-7,51,56022279121238808,scelerisque quam turpis adipiscing lorem vitae mattis nibh ligula nec sem,2023-02-22,97.21
-"King, Homenick and Conn",Dump Truck,EIFS,"Reilly, Windler and Cremin",Langworth-Ortiz,28,Bugo,546264257-1,48,4905157085342998398,cubilia curae duis faucibus accumsan odio curabitur convallis duis consequat dui nec nisi volutpat eleifend,2023-02-02,54.04
-"Koelpin, Dooley and Kuvalis",Excavator,Structural & Misc Steel Erection,Shields-Senger,McLaughlin-Koepp,48,Livadiya,470334065-8,69,0604372523721069615,mattis pulvinar nulla pede ullamcorper augue a suscipit nulla elit ac nulla,2023-04-08,67.44
-Donnelly and Sons,Crawler,Plumbing & Medical Gas,Maggio LLC,Hahn-Flatley,17,Alibago,314425716-8,83,337941169979548,enim leo rhoncus sed vestibulum sit amet cursus id turpis integer,2023-04-07,53.56
-Boyer-Medhurst,Grader,Prefabricated Aluminum Metal Canopies,"Howell, Huel and Nicolas","Deckow, Jenkins and Bartell",54,Picungbera,323060315-X,15,6706951103338750,felis fusce posuere felis sed lacus morbi sem mauris laoreet ut rhoncus aliquet pulvinar sed nisl nunc rhoncus dui vel,2022-09-19,5.46
-"Wolff, Dietrich and Kshlerin",Dragline,Masonry & Precast,"Spencer, Dooley and Ullrich","Zieme, Kuvalis and Leannon",13,Santisimo Rosario,946650016-3,65,5018417463180740,erat nulla tempus vivamus in felis eu sapien cursus vestibulum proin eu mi nulla ac enim,2023-04-17,63.98
-"Hansen, McLaughlin and Bernhard",Excavator,Drilled Shafts,"Gislason, Heaney and O'Connell",Wiegand Group,84,San Jose,445247479-9,5,201910973168492,velit nec nisi vulputate nonummy maecenas tincidunt lacus at velit vivamus vel nulla eget,2022-11-01,58.75
-Daniel LLC,Crawler,Rebar & Wire Mesh Install,"Mitchell, Barrows and Hamill",Weber-Kemmer,88,Sinjil,145272905-0,95,3537378655700793,gravida nisi at nibh in hac habitasse platea dictumst aliquam augue,2023-04-04,33.02
-"Casper, Muller and Macejkovic",Dragline,Marlite Panels (FED),"Spencer, Shields and McDermott",Rutherford and Sons,21,Pocsi,758023521-8,93,3563461502339028,pede justo lacinia eget tincidunt eget tempus vel pede morbi porttitor lorem id,2022-12-26,63.67
-Beahan-Beatty,Backhoe,Framing (Steel),Johns-Reinger,Stamm-Bergstrom,4,Buarcos,483986544-2,14,3572461996231391,vehicula condimentum curabitur in libero ut massa volutpat convallis morbi odio odio elementum eu interdum eu tincidunt in,2023-05-13,12.03
-"Schowalter, Green and Ankunding",Trencher,Soft Flooring and Base,Kreiger-Hoppe,Reynolds-Cummerata,18,San Pedro Pinula,131062548-4,15,3568617180162173,elit ac nulla sed vel enim sit amet nunc viverra dapibus nulla suscipit ligula in lacus curabitur,2022-09-27,77.12
-"Prohaska, Bosco and Abshire",Bulldozer,EIFS,Larson LLC,"Hammes, Fadel and Legros",57,Golopau,194463835-0,3,5002359023338541,vivamus vestibulum sagittis sapien cum sociis natoque penatibus et magnis dis,2022-06-30,67.31
-"Bauch, Rodriguez and Jenkins",Crawler,Masonry & Precast,"Rosenbaum, Stokes and Rau","Ferry, Huels and Sipes",86,Xiangyang,454529838-9,29,6767656690937643,est et tempus semper est quam pharetra magna ac consequat metus sapien ut nunc vestibulum ante ipsum,2022-08-22,17.81
-"Schmeler, Lockman and Stiedemann",Excavator,Framing (Steel),Kihn LLC,"Lubowitz, Roberts and Marquardt",13,Makanya,647395719-7,15,3556460198629428,quam suspendisse potenti nullam porttitor lacus at turpis donec posuere metus,2023-04-29,7.58
-Lubowitz Group,Scraper,Ornamental Railings,"Fisher, Haag and Schmidt",West LLC,10,Mabunga,787506589-5,64,3560289848556143,nam congue risus semper porta volutpat quam pede lobortis ligula sit amet eleifend pede libero quis,2022-08-01,71.58
-Hansen-Beatty,Excavator,Epoxy Flooring,Hauck-Rempel,West Inc,48,Payxambabazar,518250572-8,82,337941555646784,vivamus vel nulla eget eros elementum pellentesque quisque porta volutpat erat quisque,2022-08-19,1.48
-Herzog and Sons,Dragline,Elevator,Crist Inc,"Dooley, Wyman and Rempel",14,Kranuan,490046086-9,22,3556364929607085,arcu libero rutrum ac lobortis vel dapibus at diam nam tristique tortor eu,2023-02-04,45.0
-Lebsack-Romaguera,Dragline,Wall Protection,Hamill-Abbott,Haley-Labadie,6,Ruzayevka,731594232-7,63,3564668857002550,nunc purus phasellus in felis donec semper sapien a libero nam dui proin leo odio porttitor id consequat in,2023-03-08,13.62
-"Kirlin, Wyman and Wehner",Crawler,Structural & Misc Steel Erection,Beier LLC,Schuppe Inc,25,Shuanghe,343694285-5,67,3568605663689317,duis aliquam convallis nunc proin at turpis a pede posuere nonummy integer non velit donec,2023-03-10,34.31
-"Nader, Fadel and Bode",Bulldozer,RF Shielding,Williamson and Sons,Schmidt-Towne,68,Mölndal,607118553-X,3,340880868925813,donec ut dolor morbi vel lectus in quam fringilla rhoncus mauris,2022-06-09,28.76
-"Rohan, Wisoky and Gerhold",Dump Truck,Electrical and Fire Alarm,"Ward, Sauer and Harber",Shields-Zieme,17,Vsevolozhsk,100246862-0,39,5578993697112313,lacinia aenean sit amet justo morbi ut odio cras mi pede malesuada in imperdiet et commodo vulputate,2022-12-28,34.43
-"Goldner, Metz and Prohaska",Crawler,Roofing (Asphalt),Schamberger Inc,Reilly-Bernhard,32,Saraktash,815422723-1,98,3568940927447214,volutpat in congue etiam justo etiam pretium iaculis justo in hac habitasse platea dictumst etiam faucibus,2023-02-28,82.16
-Thompson and Sons,Dump Truck,Plumbing & Medical Gas,Russel and Sons,"Nolan, Cartwright and Ebert",31,Lívingston,617917766-X,19,3564430382302611,habitasse platea dictumst morbi vestibulum velit id pretium iaculis diam erat fermentum justo nec condimentum neque sapien placerat,2023-04-23,41.52
-"Nitzsche, Schaden and Strosin",Crawler,Electrical and Fire Alarm,Hahn and Sons,"Lehner, Hayes and Grant",22,Gennevilliers,860027339-0,13,5641827092783026,libero rutrum ac lobortis vel dapibus at diam nam tristique tortor eu,2023-05-08,19.4
-Larkin LLC,Dragline,Roofing (Metal),Fay-Erdman,Nolan and Sons,20,San Agustin,906705311-2,82,3531330383605949,felis ut at dolor quis odio consequat varius integer ac leo pellentesque ultrices mattis odio donec vitae,2022-11-30,58.55
-Feest Group,Compactor,Ornamental Railings,"Rowe, Bins and Torp",Romaguera-Greenholt,27,Ribeira Seca,842784499-9,88,3580033745274830,nisi nam ultrices libero non mattis pulvinar nulla pede ullamcorper augue a suscipit nulla elit,2023-01-22,54.88
-Osinski Inc,Bulldozer,Epoxy Flooring,"Abernathy, Huels and Greenfelder","Jaskolski, Sporer and Corwin",54,Mae Sai,627022315-6,71,5594694799398897,ipsum dolor sit amet consectetuer adipiscing elit proin interdum mauris non ligula pellentesque ultrices phasellus id sapien in sapien iaculis,2022-09-30,44.05
-Bauch Inc,Backhoe,Prefabricated Aluminum Metal Canopies,Howell-Rogahn,Hessel-Nienow,50,Mayisad,619216443-6,90,3557488995743232,in purus eu magna vulputate luctus cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus vivamus vestibulum,2023-02-23,37.19
-Welch Inc,Skid-Steer,Casework,"Grant, Fadel and Macejkovic","Towne, Johnston and Rice",1,Huarong,533477095-X,93,345254385886483,placerat ante nulla justo aliquam quis turpis eget elit sodales scelerisque,2022-12-27,18.46
-"Boehm, Windler and Konopelski",Compactor,"Doors, Frames & Hardware",Hansen-Turcotte,Beier-Hermiston,94,Buyunshan,149301071-9,50,5380142384134598,eleifend pede libero quis orci nullam molestie nibh in lectus pellentesque at nulla suspendisse,2023-04-14,62.31
-"McGlynn, Zulauf and Hauck",Trencher,Retaining Wall and Brick Pavers,Beahan-Bernhard,Gutmann LLC,44,Nanjie,647091867-0,71,5641826426694164,dapibus augue vel accumsan tellus nisi eu orci mauris lacinia sapien quis libero nullam sit amet turpis,2022-07-25,31.94
-Howe-Powlowski,Skid-Steer,Drilled Shafts,Toy Group,Medhurst LLC,81,Saramech,156653384-8,64,3538154733411268,ut dolor morbi vel lectus in quam fringilla rhoncus mauris enim leo rhoncus sed vestibulum sit amet,2022-07-31,16.01
-"Dach, Trantow and Larson",Excavator,Structural and Misc Steel (Fabrication),"Hagenes, Stamm and Wisoky",Bosco-Anderson,9,Patos,471131697-3,92,4017957124324,curabitur convallis duis consequat dui nec nisi volutpat eleifend donec ut dolor,2023-02-16,46.16
-"Weber, McCullough and Champlin",Scraper,Casework,"D'Amore, Lebsack and Stoltenberg","Herman, Beahan and Willms",65,Chaloem Phra Kiat,703706719-9,82,56022380102896588,turpis enim blandit mi in porttitor pede justo eu massa donec dapibus duis at velit,2022-06-12,48.58
+Kovacek and Sons,Dragline,Roofing (Metal),Runte-Corwin,Cremin Inc,61,Artashat,017019279-2,59,3558344370409403,sit amet diam in magna bibendum imperdiet nullam orci pede venenatis non sodales sed tincidunt eu felis,8/24/22,73.96
+"Russel, Marquardt and Frami",Skid-Steer,Drywall & Acoustical (MOB),Bins-Gerhold,Breitenberg Inc,17,Dugcal,801930572-6,10,3.01043E+13,diam cras pellentesque volutpat dui maecenas tristique est et tempus semper est quam pharetra magna ac,6/24/22,5.27
+Roob-Mante,Dump Truck,Electrical and Fire Alarm,"Rath, Mueller and Halvorson","Bogisich, Schaefer and Goldner",14,Askiz,778878918-6,58,3578492711349816,donec ut mauris eget massa tempor convallis nulla neque libero convallis eget eleifend luctus,3/30/23,57.51
\ No newline at end of file
diff --git a/sample_csvs/consumables-sample.csv b/sample_csvs/consumables-sample.csv
index 962f55ebd..dd9c8df0a 100644
--- a/sample_csvs/consumables-sample.csv
+++ b/sample_csvs/consumables-sample.csv
@@ -1,51 +1,4 @@
Company,Name,Category,Supplier,Manufacturer,QTY,Location,Order Number,Min Amount,Model Number,Item Number,Notes,Purchase Date,Purchase Cost
-Simonis and Sons,Dragline,Soft Flooring and Base,Towne Group,McLaughlin-Wisozk,50,Cherven Bryag,529027385-9,51,3533069599460644,6381392801995066,tortor sollicitudin mi sit amet lobortis sapien sapien non mi integer ac neque duis bibendum morbi non quam nec,2022-11-05,35.2
-Bruen-Hagenes,Trencher,Sitework & Site Utilities,Hoeger and Sons,"Davis, Koelpin and Lockman",54,Pampas,009135228-2,92,5602248408566255894,3563525570373877,fusce lacus purus aliquet at feugiat non pretium quis lectus suspendisse potenti in eleifend quam a odio in hac habitasse,2022-09-17,53.1
-Tremblay Inc,Crawler,Drywall & Acoustical (FED),"Cormier, Ryan and Cummerata","Morar, Stracke and Gusikowski",97,Penteado,492530513-1,20,201715355999350,3555797000872238,massa tempor convallis nulla neque libero convallis eget eleifend luctus ultricies eu,2023-02-05,25.08
-Franecki-Mitchell,Crawler,Soft Flooring and Base,"Bahringer, Hessel and Lynch",Mann Inc,11,Junliangcheng,006782510-9,13,201638625742873,3585582428645232,orci luctus et ultrices posuere cubilia curae nulla dapibus dolor vel est donec,2023-01-25,47.44
-Morar Group,Trencher,Masonry,Metz-Runte,Batz and Sons,35,Migori,410276787-8,2,201574561109571,4041593804503,sit amet consectetuer adipiscing elit proin risus praesent lectus vestibulum quam sapien varius,2022-08-24,29.06
-Rau and Sons,Crawler,Exterior Signage,"Metz, Reynolds and Pagac",Roberts-Haley,36,Lužani,251681998-6,37,6389706621209402,67627057199235112,mauris ullamcorper purus sit amet nulla quisque arcu libero rutrum ac lobortis vel dapibus,2022-09-25,70.5
-"Parisian, Feil and Brown",Dragline,Site Furnishings,"Wisozk, Rowe and Runolfsson",Marks-Lindgren,24,Carvalho,783104099-4,96,4132814261323213,5460576255071343,justo sollicitudin ut suscipit a feugiat et eros vestibulum ac,2022-06-06,23.48
-Upton Group,Skid-Steer,Wall Protection,Sawayn-Predovic,Padberg-Schaefer,86,Drumcondra,691167736-X,20,6709802030161855,5100134953726119,luctus et ultrices posuere cubilia curae mauris viverra diam vitae,2022-09-13,24.6
-Olson-Bradtke,Skid-Steer,Structural and Misc Steel (Fabrication),Dietrich Group,"Doyle, Yost and Kerluke",52,Piława Górna,993878516-6,99,6380721061950737,201534062027276,sed sagittis nam congue risus semper porta volutpat quam pede lobortis ligula sit amet eleifend,2023-03-09,64.2
-Pouros-Veum,Backhoe,Elevator,Gulgowski-Bahringer,Legros-Morissette,85,Tawangsari,863628920-3,27,5100136112680129,5602259943208580,aliquet ultrices erat tortor sollicitudin mi sit amet lobortis sapien sapien non mi integer ac neque duis bibendum morbi non,2022-09-10,73.65
-Purdy-Rice,Grader,Drywall & Acoustical (MOB),Bernhard-Cummings,Price and Sons,81,Trzebinia,184211928-1,43,5602251551839830,56022542102331797,mauris laoreet ut rhoncus aliquet pulvinar sed nisl nunc rhoncus dui,2022-08-20,12.11
-Kihn and Sons,Trencher,EIFS,"Auer, Kutch and Kutch","Murazik, Crooks and Pollich",73,Duru,843046019-5,32,3570560403003822,3544501358533481,tincidunt in leo maecenas pulvinar lobortis est phasellus sit amet erat nulla tempus,2023-04-03,26.73
-Hessel-Yundt,Bulldozer,Wall Protection,O'Reilly Inc,Volkman LLC,40,Yelizavetino,358328061-X,50,5108759813450674,5602215719381357,nulla dapibus dolor vel est donec odio justo sollicitudin ut suscipit a feugiat et eros,2023-05-14,83.76
-Kunze and Sons,Bulldozer,Drywall & Acoustical (MOB),Pollich-Mertz,Kertzmann-Leffler,91,Hoeryŏng,676937901-5,50,5602259559696912,3536485956609903,eget massa tempor convallis nulla neque libero convallis eget eleifend luctus ultricies eu nibh quisque id justo sit amet sapien,2023-02-06,14.1
-"Satterfield, Kutch and Bartell",Crawler,Construction Clean and Final Clean,Prohaska-Rosenbaum,O'Conner and Sons,8,Verenchanka,822285715-0,59,36675923976242,3555834547028303,nec dui luctus rutrum nulla tellus in sagittis dui vel nisl duis ac nibh fusce lacus purus aliquet,2023-03-22,36.15
-Stanton-Olson,Dump Truck,Marlite Panels (FED),"Stiedemann, Jones and Heathcote",Bartoletti Inc,17,Mórrope,230874108-2,70,3573310923840155,5602252823412497,tellus in sagittis dui vel nisl duis ac nibh fusce lacus purus aliquet at feugiat non pretium,2022-11-03,14.22
-Kshlerin-Hane,Trencher,Curb & Gutter,Goodwin and Sons,Bechtelar-Gleason,45,Shūsh,451367111-4,46,3536005374229934,5438118681407699,eleifend quam a odio in hac habitasse platea dictumst maecenas ut massa quis augue luctus tincidunt nulla mollis molestie lorem,2023-05-03,15.14
-"Mills, Greenfelder and Spinka",Crawler,HVAC,Reichel Inc,"Kuhic, Marquardt and Beier",10,Arcena Pequena,779807440-6,13,3552031889209913,5018365634108542731,pellentesque ultrices mattis odio donec vitae nisi nam ultrices libero non mattis pulvinar nulla pede ullamcorper augue a suscipit,2022-11-09,57.04
-Buckridge Inc,Skid-Steer,Asphalt Paving,Abernathy-Doyle,Hirthe-Gislason,23,Cardal,152277775-X,65,5602239497538228,6304534980889636,et commodo vulputate justo in blandit ultrices enim lorem ipsum dolor sit amet consectetuer adipiscing elit,2023-05-02,48.68
-Boehm Inc,Skid-Steer,Electrical and Fire Alarm,O'Kon Group,Schuster-Kunze,63,Tafraout,072198056-2,67,3536207535355414,5602242007322746026,donec ut mauris eget massa tempor convallis nulla neque libero convallis eget eleifend luctus ultricies eu nibh quisque id,2022-07-20,28.04
-Maggio Inc,Crawler,Curb & Gutter,West-Batz,Homenick-Schamberger,45,Boa Vista,779589028-8,16,3544589458568846,374622740975098,dolor vel est donec odio justo sollicitudin ut suscipit a,2023-05-14,32.86
-Koelpin Inc,Skid-Steer,"Doors, Frames & Hardware",VonRueden Group,Dickinson Group,6,Cachoeirinha,474127645-7,3,633110118810265152,4936757035760145,ante vel ipsum praesent blandit lacinia erat vestibulum sed magna at nunc commodo placerat praesent blandit nam nulla integer,2022-08-13,5.24
-Shields-Mohr,Bulldozer,Overhead Doors,Lesch-Daniel,Kunde-Dickens,11,Požarevac,658756723-1,19,3574428328762235,3582676636622299,semper sapien a libero nam dui proin leo odio porttitor id consequat in consequat ut nulla sed accumsan felis,2023-03-10,73.7
-Bechtelar-Littel,Skid-Steer,RF Shielding,Smitham-Yost,Cormier Inc,44,Cikendi,650173691-9,30,630469977163834077,4917512293781355,condimentum id luctus nec molestie sed justo pellentesque viverra pede ac diam cras,2022-05-23,99.17
-"Botsford, Rogahn and Heathcote",Compactor,Termite Control,Marquardt-Fadel,Klein-Kohler,71,Kučevo,792535885-8,51,4903441380678636477,5602259771893871162,lorem vitae mattis nibh ligula nec sem duis aliquam convallis nunc proin,2023-04-20,70.22
-"Fadel, Abbott and Renner",Grader,Curb & Gutter,Collier-Halvorson,Heidenreich-Wisozk,34,Siparia,344682803-6,19,3558289528892176,5341222466132468,dui luctus rutrum nulla tellus in sagittis dui vel nisl duis ac nibh fusce lacus purus aliquet,2022-12-01,68.35
-"Klein, Stehr and Hansen",Bulldozer,Electrical,Heathcote-Hegmann,"Schoen, Schinner and Bashirian",70,Kobyzhcha,747563100-1,3,67615918374813979,3552267916495436,iaculis justo in hac habitasse platea dictumst etiam faucibus cursus urna ut tellus,2022-11-23,36.02
-Dibbert-Sawayn,Excavator,Epoxy Flooring,Purdy Inc,Jacobs LLC,90,Fukumitsu,684367116-0,89,3575124426928351,675940744647511521,vulputate ut ultrices vel augue vestibulum ante ipsum primis in faucibus orci luctus et ultrices,2022-08-25,11.9
-Smith-Greenholt,Skid-Steer,Roofing (Metal),Leannon-Swaniawski,Brown-O'Connell,48,Nanwai,182125694-8,21,3549394673501796,3534352097481265,vestibulum sed magna at nunc commodo placerat praesent blandit nam nulla integer pede justo lacinia eget,2022-09-29,74.89
-Ratke and Sons,Trencher,Painting & Vinyl Wall Covering,Pfannerstill LLC,Williamson and Sons,62,Shūkat aş Şūfī,894744686-6,63,67627557381842583,3575485459195667,elit ac nulla sed vel enim sit amet nunc viverra dapibus nulla suscipit ligula in,2023-02-09,58.2
-Hammes Group,Compactor,Elevator,Hammes-Ortiz,Gusikowski Group,49,Norton,142503650-3,16,4041373495456,3567365631500028,morbi vel lectus in quam fringilla rhoncus mauris enim leo,2023-02-04,77.95
-Bednar-Tillman,Grader,Structural & Misc Steel Erection,Hackett-Rice,"Roberts, Grimes and Feest",48,Aengceleng,027615376-6,77,5038353282556401349,379827830457023,venenatis lacinia aenean sit amet justo morbi ut odio cras mi pede malesuada in imperdiet,2022-06-14,66.55
-Conn Inc,Backhoe,Elevator,"Satterfield, Dietrich and Dibbert","Gislason, Turner and Weissnat",49,Dajianchang,012542390-X,22,4175009765287943,633422970094073881,eleifend luctus ultricies eu nibh quisque id justo sit amet sapien dignissim vestibulum vestibulum ante ipsum primis,2023-04-24,39.65
-Baumbach-Hayes,Scraper,HVAC,Larkin-Stamm,Romaguera Group,60,Pshada,975951565-2,17,3558709467530792,201740170261422,mattis odio donec vitae nisi nam ultrices libero non mattis pulvinar nulla pede,2023-03-28,2.13
-Johnson-Hamill,Bulldozer,Electrical and Fire Alarm,Brekke-Rau,"Doyle, Crist and Kulas",87,Kwikila,420068378-4,44,3572846952864955,5610350660093584,maecenas pulvinar lobortis est phasellus sit amet erat nulla tempus,2022-11-13,55.16
-Weber-Spencer,Trencher,Structural & Misc Steel Erection,Jenkins-Nienow,Crona Group,17,Shiojiri,607345620-4,88,6387217570136176,6371741739648696,est phasellus sit amet erat nulla tempus vivamus in felis eu sapien cursus,2022-12-05,91.78
-Bartell-Wisozk,Dump Truck,Framing (Steel),Robel LLC,Bartell Inc,60,Bourg-en-Bresse,285063128-0,65,348798971763230,3529839890242313,in congue etiam justo etiam pretium iaculis justo in hac habitasse,2022-09-10,15.19
-"Blick, Wunsch and Kreiger",Backhoe,Electrical,Conn and Sons,Lockman and Sons,14,Falāvarjān,141897809-4,15,3559869406780764,5602254605681417512,adipiscing elit proin interdum mauris non ligula pellentesque ultrices phasellus,2022-06-14,72.14
-Cassin and Sons,Trencher,Curb & Gutter,Weimann Inc,"Schumm, Smith and Ortiz",83,Caramuca,054429347-9,39,3584248684102141,3533548146815194,quis turpis sed ante vivamus tortor duis mattis egestas metus aenean fermentum donec ut mauris eget massa tempor,2022-06-22,34.35
-"Miller, Boehm and Kovacek",Grader,Structural & Misc Steel Erection,Heller-Simonis,"Harvey, Schiller and Schultz",60,Puerto Alto,463560912-X,99,3540539014169112,6304647611299330905,tristique in tempus sit amet sem fusce consequat nulla nisl nunc,2023-04-25,56.23
-Gulgowski Inc,Dump Truck,Prefabricated Aluminum Metal Canopies,Watsica-Bogan,"Toy, Fahey and McKenzie",77,Petrikov,662823771-0,41,3532416020888375,201871804426022,orci luctus et ultrices posuere cubilia curae duis faucibus accumsan,2023-01-07,28.79
-Beahan-Boyer,Bulldozer,Fire Protection,"Mayer, Stark and Blanda","Grant, Huel and O'Connell",40,Los Angeles,388651272-X,7,3540719793054384,3565693812607524,mauris viverra diam vitae quam suspendisse potenti nullam porttitor lacus at turpis donec posuere metus,2023-03-11,94.98
-"Rau, Sanford and Dach",Compactor,Fire Protection,"Ziemann, Hudson and Berge",Rempel Inc,32,Dapdap,257178615-6,100,3567797431604172,36788101946745,vel enim sit amet nunc viverra dapibus nulla suscipit ligula in lacus curabitur at ipsum,2022-10-22,62.54
-"Hettinger, Pollich and Gleason",Dump Truck,Drywall & Acoustical (MOB),Huel Group,Friesen LLC,26,Lodhrān,942559838-X,53,375713644821956,4913216453197543,tristique fusce congue diam id ornare imperdiet sapien urna pretium nisl ut volutpat sapien arcu,2023-01-01,96.74
-Schultz and Sons,Crawler,Masonry & Precast,"Dach, Doyle and Dickens","Skiles, Fisher and Koepp",44,Xinxing,003878835-7,20,676195016446613968,6304826401904593789,nisl ut volutpat sapien arcu sed augue aliquam erat volutpat in congue etiam justo etiam pretium iaculis justo in hac,2023-02-04,33.83
-McLaughlin-Ratke,Backhoe,Electrical and Fire Alarm,Berge-Wilkinson,Maggio and Sons,36,Bantar,651286142-6,47,5100130235429332,6706505880344645,sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus etiam vel augue vestibulum rutrum rutrum neque aenean,2022-11-22,3.68
-Wehner-Kuhlman,Excavator,Construction Clean and Final Clean,"Thiel, Roob and Corwin","McGlynn, Padberg and Bogan",83,Kirgili,561724673-9,31,374288950680370,3580557960145994,amet turpis elementum ligula vehicula consequat morbi a ipsum integer,2023-02-28,7.54
-Koelpin-Dicki,Grader,Ornamental Railings,Herzog LLC,"Marquardt, Berge and Corkery",19,Srubec,758267626-2,12,6396431946998362,4175005352440697,semper rutrum nulla nunc purus phasellus in felis donec semper sapien a libero nam dui proin leo odio porttitor id,2023-03-12,18.2
-Adams and Sons,Backhoe,Roofing (Asphalt),"Will, Paucek and Luettgen",Hane-Heaney,81,Huifeng,925057692-7,51,5350965259826013,5381044667065516,sed lacus morbi sem mauris laoreet ut rhoncus aliquet pulvinar sed nisl,2022-07-24,80.03
-Greenholt and Sons,Crawler,Ornamental Railings,Grant-Stroman,Gottlieb-Lebsack,27,Trondheim,344790479-8,39,5100143050486022,67715312291635427,velit vivamus vel nulla eget eros elementum pellentesque quisque porta,2023-04-18,12.77
+Simonis and Sons,Dragline,Soft Flooring and Base,Towne Group Two,McLaughlin-Wisozk,50,Cherven Bryag,529027385-9,100,3533069599460644,6381392801995066,tortor sollicitudin mi sit amet lobortis sapien sapien non mi integer ac neque duis bibendum morbi non quam nec,11/5/22,35.2
+Bruen-Hagenes,Trencher,Sitework & Site Utilities,Hoeger and Sons,"Davis, Koelpin and Lockman",54,Pampas,009135228-2,92,5602248408566255894,3563525570373877,fusce lacus purus aliquet at feugiat non pretium quis lectus suspendisse potenti in eleifend quam a odio in hac habitasse,9/17/22,53.1
+Tremblay Inc,Crawler,Drywall & Acoustical (FED),"Cormier, Ryan and Cummerata","Morar, Stracke and Gusikowski",97,Penteado,492530513-1,20,56022484085662558554,3555797000872238,massa tempor convallis nulla neque libero convallis eget eleifend luctus ultricies eu,2/5/23,25.08
\ No newline at end of file
diff --git a/snipeit.sh b/snipeit.sh
index cce33de36..121c34280 100755
--- a/snipeit.sh
+++ b/snipeit.sh
@@ -428,7 +428,7 @@ case $distro in
progress
echo "* Installing Apache httpd, PHP, MariaDB and other requirements."
- PACKAGES="mariadb-server mariadb-client apache2 libapache2-mod-php8.2 php8.2 php8.2-mcrypt php8.2-curl php8.2-mysql php8.2-gd php8.2-ldap php8.2-zip php8.2-mbstring php8.2-xml php8.2-bcmath curl git unzip"
+ PACKAGES="mariadb-server mariadb-client apache2 libapache2-mod-php8.2 php8.2 php8.2-curl php8.2-mysql php8.2-gd php8.2-ldap php8.2-zip php8.2-mbstring php8.2-xml php8.2-bcmath curl git unzip"
install_packages
echo "* Configuring Apache."
@@ -465,7 +465,7 @@ case $distro in
progress
echo "* Installing Apache httpd, PHP, MariaDB and other requirements."
- PACKAGES="mariadb-server mariadb-client apache2 libapache2-mod-php8.2 php8.2 php8.2-mcrypt php8.2-curl php8.2-mysql php8.2-gd php8.2-ldap php8.2-zip php8.2-mbstring php8.2-xml php8.2-bcmath curl git unzip"
+ PACKAGES="mariadb-server mariadb-client apache2 libapache2-mod-php8.2 php8.2 php8.2-curl php8.2-mysql php8.2-gd php8.2-ldap php8.2-zip php8.2-mbstring php8.2-xml php8.2-bcmath curl git unzip"
install_packages
echo "* Configuring Apache."
@@ -502,7 +502,7 @@ case $distro in
progress
echo "* Installing Apache httpd, PHP, MariaDB and other requirements."
- PACKAGES="mariadb-server mariadb-client apache2 libapache2-mod-php8.2 php8.2 php8.2-mcrypt php8.2-curl php8.2-mysql php8.2-gd php8.2-ldap php8.2-zip php8.2-mbstring php8.2-xml php8.2-bcmath curl git unzip"
+ PACKAGES="mariadb-server mariadb-client apache2 libapache2-mod-php8.2 php8.2 php8.2-curl php8.2-mysql php8.2-gd php8.2-ldap php8.2-zip php8.2-mbstring php8.2-xml php8.2-bcmath curl git unzip"
install_packages
echo "* Configuring Apache."
@@ -543,7 +543,7 @@ case $distro in
progress
echo "* Installing Apache httpd, PHP, MariaDB and other requirements."
- PACKAGES="cron mariadb-server mariadb-client apache2 libapache2-mod-php php php-mcrypt php-curl php-mysql php-gd php-ldap php-zip php-mbstring php-xml php-bcmath curl git unzip"
+ PACKAGES="cron mariadb-server mariadb-client apache2 libapache2-mod-php php php-curl php-mysql php-gd php-ldap php-zip php-mbstring php-xml php-bcmath curl git unzip"
install_packages
echo "* Configuring Apache."
@@ -584,7 +584,7 @@ case $distro in
progress
echo "* Installing Apache httpd, PHP, MariaDB and other requirements."
- PACKAGES="cron mariadb-server mariadb-client apache2 libapache2-mod-php8.2 php8.2 php8.2-mcrypt php8.2-curl php8.2-mysql php8.2-gd php8.2-ldap php8.2-zip php8.2-mbstring php8.2-xml php8.2-bcmath curl git unzip"
+ PACKAGES="cron mariadb-server mariadb-client apache2 libapache2-mod-php8.2 php8.2 php8.2-curl php8.2-mysql php8.2-gd php8.2-ldap php8.2-zip php8.2-mbstring php8.2-xml php8.2-bcmath curl git unzip"
install_packages
echo "* Configuring Apache."
@@ -628,7 +628,7 @@ case $distro in
progress
echo "* Installing Apache httpd, PHP, MariaDB and other requirements."
- PACKAGES="cron mariadb-server mariadb-client apache2 libapache2-mod-php8.28.2 php8.2 php8.2-mcrypt php8.2-curl php8.2-mysql php8.2-gd php8.2-ldap php8.2-zip php8.2-mbstring php8.2-xml php8.2-bcmath curl git unzip"
+ PACKAGES="cron mariadb-server mariadb-client apache2 libapache2-mod-php8.28.2 php8.2 php8.2-curl php8.2-mysql php8.2-gd php8.2-ldap php8.2-zip php8.2-mbstring php8.2-xml php8.2-bcmath curl git unzip"
install_packages
echo "* Configuring Apache."
@@ -688,12 +688,11 @@ EOL
progress
echo "* Installing Apache httpd, PHP, MariaDB and other requirements."
- PACKAGES="mariadb-server mariadb-client apache2 libapache2-mod-php8.2 php8.2 php8.2-mcrypt php8.2-curl php8.2-mysql php8.2-gd php8.2-ldap php8.2-zip php8.2-mbstring php8.2-xml php8.2-bcmath curl git unzip"
+ PACKAGES="mariadb-server mariadb-client apache2 libapache2-mod-php8.2 php8.2 php8.2-curl php8.2-mysql php8.2-gd php8.2-ldap php8.2-zip php8.2-mbstring php8.2-xml php8.2-bcmath curl git unzip"
install_packages
echo "* Configuring Apache."
create_virtualhost
- log "phpenmod mcrypt"
log "phpenmod mbstring"
log "a2enmod rewrite"
log "a2ensite $APP_NAME.conf"
@@ -728,7 +727,7 @@ EOL
amazon-linux-extras install -y php8.2
echo "* Installing Apache httpd, PHP, MariaDB and other requirements."
- PACKAGES="httpd mariadb-server git unzip php php-mysqlnd php-bcmath php-embedded php-gd php-mbstring php-mcrypt php-ldap php-json php-simplexml php-process php-zip php-sodium"
+ PACKAGES="httpd mariadb-server git unzip php php-mysqlnd php-bcmath php-embedded php-gd php-mbstring php-ldap php-json php-simplexml php-process php-zip php-sodium"
install_packages
echo "* Configuring Apache."
@@ -769,7 +768,7 @@ EOL
log "yum-config-manager --enable remi-php82"
echo "* Installing Apache httpd, PHP, MariaDB and other requirements."
- PACKAGES="httpd mariadb-server git unzip php php-mysqlnd php-bcmath php-embedded php-gd php-mbstring php-mcrypt php-ldap php-json php-simplexml php-process php-zip"
+ PACKAGES="httpd mariadb-server git unzip php php-mysqlnd php-bcmath php-embedded php-gd php-mbstring php-ldap php-json php-simplexml php-process php-zip"
install_packages
echo "* Configuring Apache."
@@ -812,7 +811,7 @@ EOL
progress
echo "* Installing Apache httpd, PHP, MariaDB and other requirements."
- PACKAGES="httpd mariadb-server git unzip php php-mysqlnd php-bcmath php-embedded php-gd php-mbstring php-mcrypt php-ldap php-json php-simplexml php-process php-zip"
+ PACKAGES="httpd mariadb-server git unzip php php-mysqlnd php-bcmath php-embedded php-gd php-mbstring php-ldap php-json php-simplexml php-process php-zip"
install_packages
echo "* Configuring Apache."
diff --git a/tests/Feature/Accessories/Ui/ShowAccessoryTest.php b/tests/Feature/Accessories/Ui/ShowAccessoryTest.php
index f9f2e566c..288ec9a50 100644
--- a/tests/Feature/Accessories/Ui/ShowAccessoryTest.php
+++ b/tests/Feature/Accessories/Ui/ShowAccessoryTest.php
@@ -12,7 +12,7 @@ class ShowAccessoryTest extends TestCase
public function testRequiresPermissionToViewAccessory()
{
$this->actingAs(User::factory()->create())
- ->get(route('accessories.show', Accessory::factory()->create()->id))
+ ->get(route('accessories.show', Accessory::factory()->create()))
->assertForbidden();
}
@@ -25,8 +25,8 @@ class ShowAccessoryTest extends TestCase
$userForCompanyB = User::factory()->for($companyB)->viewAccessories()->create();
$this->actingAs($userForCompanyB)
- ->get(route('accessories.show', $accessoryForCompanyA->id))
- ->assertForbidden();
+ ->get(route('accessories.show', $accessoryForCompanyA))
+ ->assertStatus(302);
}
public function testCanViewAccessory()
@@ -34,7 +34,7 @@ class ShowAccessoryTest extends TestCase
$accessory = Accessory::factory()->create();
$this->actingAs(User::factory()->viewAccessories()->create())
- ->get(route('accessories.show', $accessory->id))
+ ->get(route('accessories.show', $accessory))
->assertOk()
->assertViewIs('accessories.view')
->assertViewHas(['accessory' => $accessory]);
@@ -43,7 +43,7 @@ class ShowAccessoryTest extends TestCase
public function testPageRenders()
{
$this->actingAs(User::factory()->superuser()->create())
- ->get(route('accessories.show', Accessory::factory()->create()->id))
+ ->get(route('accessories.show', Accessory::factory()->create()))
->assertOk();
}
diff --git a/tests/Feature/Accessories/Ui/UpdateAccessoryTest.php b/tests/Feature/Accessories/Ui/UpdateAccessoryTest.php
index 1c6fe8a49..8c482b4f1 100644
--- a/tests/Feature/Accessories/Ui/UpdateAccessoryTest.php
+++ b/tests/Feature/Accessories/Ui/UpdateAccessoryTest.php
@@ -16,7 +16,7 @@ class UpdateAccessoryTest extends TestCase
public function testRequiresPermissionToSeeEditAccessoryPage()
{
$this->actingAs(User::factory()->create())
- ->get(route('accessories.edit', Accessory::factory()->create()->id))
+ ->get(route('accessories.edit', Accessory::factory()->create()))
->assertForbidden();
}
diff --git a/tests/Feature/AssetMaintenances/Ui/CreateAssetMaintenanceTest.php b/tests/Feature/AssetMaintenances/Ui/CreateAssetMaintenanceTest.php
index 6bfa46ed9..e50cd50a1 100644
--- a/tests/Feature/AssetMaintenances/Ui/CreateAssetMaintenanceTest.php
+++ b/tests/Feature/AssetMaintenances/Ui/CreateAssetMaintenanceTest.php
@@ -2,6 +2,8 @@
namespace Tests\Feature\AssetMaintenances\Ui;
+use App\Models\Asset;
+use App\Models\Supplier;
use App\Models\User;
use Tests\TestCase;
@@ -13,4 +15,41 @@ class CreateAssetMaintenanceTest extends TestCase
->get(route('maintenances.create'))
->assertOk();
}
+
+ public function testCanCreateAssetMaintenance()
+ {
+ $actor = User::factory()->superuser()->create();
+
+ $asset = Asset::factory()->create();
+ $supplier = Supplier::factory()->create();
+
+ $this->actingAs($actor)
+ ->followingRedirects()
+ ->post(route('maintenances.store'), [
+ 'title' => 'Test Maintenance',
+ 'asset_id' => $asset->id,
+ 'supplier_id' => $supplier->id,
+ 'asset_maintenance_type' => 'Maintenance',
+ 'start_date' => '2021-01-01',
+ 'completion_date' => '2021-01-10',
+ 'is_warranty' => '1',
+ 'cost' => '100.00',
+ 'notes' => 'A note',
+ ])
+ ->assertOk();
+
+ $this->assertDatabaseHas('asset_maintenances', [
+ 'asset_id' => $asset->id,
+ 'supplier_id' => $supplier->id,
+ 'asset_maintenance_type' => 'Maintenance',
+ 'title' => 'Test Maintenance',
+ 'is_warranty' => 1,
+ 'start_date' => '2021-01-01',
+ 'completion_date' => '2021-01-10',
+ 'asset_maintenance_time' => '9',
+ 'notes' => 'A note',
+ 'cost' => '100.00',
+ 'created_by' => $actor->id,
+ ]);
+ }
}
diff --git a/tests/Feature/AssetMaintenances/Ui/EditAssetMaintenanceTest.php b/tests/Feature/AssetMaintenances/Ui/EditAssetMaintenanceTest.php
index 0b3b68108..0e0f5b81c 100644
--- a/tests/Feature/AssetMaintenances/Ui/EditAssetMaintenanceTest.php
+++ b/tests/Feature/AssetMaintenances/Ui/EditAssetMaintenanceTest.php
@@ -2,7 +2,9 @@
namespace Tests\Feature\AssetMaintenances\Ui;
+use App\Models\Asset;
use App\Models\AssetMaintenance;
+use App\Models\Supplier;
use App\Models\User;
use Tests\TestCase;
@@ -14,4 +16,43 @@ class EditAssetMaintenanceTest extends TestCase
->get(route('maintenances.edit', AssetMaintenance::factory()->create()->id))
->assertOk();
}
+
+ public function testCanUpdateAssetMaintenance()
+ {
+ $actor = User::factory()->superuser()->create();
+
+ $assetMaintenance = AssetMaintenance::factory()->create();
+
+ $asset = Asset::factory()->create();
+ $supplier = Supplier::factory()->create();
+
+ $this->actingAs($actor)
+ ->followingRedirects()
+ ->put(route('maintenances.update', $assetMaintenance->id), [
+ 'title' => 'Test Maintenance',
+ 'asset_id' => $asset->id,
+ 'supplier_id' => $supplier->id,
+ 'asset_maintenance_type' => 'Maintenance',
+ 'start_date' => '2021-01-01',
+ 'completion_date' => '2021-01-10',
+ 'is_warranty' => '1',
+ 'cost' => '100.00',
+ 'notes' => 'A note',
+ ])
+ ->assertOk();
+
+ $this->assertDatabaseHas('asset_maintenances', [
+ 'asset_id' => $asset->id,
+ 'supplier_id' => $supplier->id,
+ 'asset_maintenance_type' => 'Maintenance',
+ 'title' => 'Test Maintenance',
+ 'is_warranty' => 1,
+ 'start_date' => '2021-01-01',
+ 'completion_date' => '2021-01-10',
+ 'asset_maintenance_time' => '9',
+ 'notes' => 'A note',
+ 'cost' => '100.00',
+ ]);
+ }
+
}
diff --git a/tests/Feature/AssetModels/Api/AssetModelFilesTest.php b/tests/Feature/AssetModels/Api/AssetModelFilesTest.php
index c22609c0c..8df441f2a 100644
--- a/tests/Feature/AssetModels/Api/AssetModelFilesTest.php
+++ b/tests/Feature/AssetModels/Api/AssetModelFilesTest.php
@@ -43,11 +43,10 @@ class AssetModelFilesTest extends TestCase
->getJson(
route('api.models.files.index', ['model_id' => $model[0]["id"]]))
->assertOk()
- ->assertJsonStructure([
- 'status',
- 'messages',
- 'payload',
- ]);
+ ->assertJsonStructure([
+ 'rows',
+ 'total',
+ ]);
}
public function testAssetModelApiDownloadsFile()
@@ -66,20 +65,25 @@ class AssetModelFilesTest extends TestCase
route('api.models.files.store', ['model_id' => $model[0]["id"]]), [
'file' => [UploadedFile::fake()->create("test.jpg", 100)]
])
- ->assertOk();
+ ->assertOk()
+ ->assertJsonStructure([
+ 'status',
+ 'messages',
+ ]);
// List the files to get the file ID
$result = $this->actingAsForApi($user)
->getJson(
route('api.models.files.index', ['model_id' => $model[0]["id"]]))
- ->assertOk();
+ ->assertOk();
+
// Get the file
$this->actingAsForApi($user)
->get(
route('api.models.files.show', [
'model_id' => $model[0]["id"],
- 'file_id' => $result->decodeResponseJson()->json()["payload"][0]["id"],
+ 'file_id' => $result->decodeResponseJson()->json()["rows"][0]["id"],
]))
->assertOk();
}
@@ -113,8 +117,12 @@ class AssetModelFilesTest extends TestCase
->delete(
route('api.models.files.destroy', [
'model_id' => $model[0]["id"],
- 'file_id' => $result->decodeResponseJson()->json()["payload"][0]["id"],
+ 'file_id' => $result->decodeResponseJson()->json()["rows"][0]["id"],
]))
- ->assertOk();
+ ->assertOk()
+ ->assertJsonStructure([
+ 'status',
+ 'messages',
+ ]);
}
}
diff --git a/tests/Feature/AssetModels/Ui/ShowAssetModelsTest.php b/tests/Feature/AssetModels/Ui/ShowAssetModelsTest.php
index 8b4104adc..739112f59 100644
--- a/tests/Feature/AssetModels/Ui/ShowAssetModelsTest.php
+++ b/tests/Feature/AssetModels/Ui/ShowAssetModelsTest.php
@@ -11,7 +11,7 @@ class ShowAssetModelsTest extends TestCase
public function testPageRenders()
{
$this->actingAs(User::factory()->superuser()->create())
- ->get(route('models.show', AssetModel::factory()->create()->id))
+ ->get(route('models.show', AssetModel::factory()->create()))
->assertOk();
}
}
diff --git a/tests/Feature/AssetModels/Ui/UpdateAssetModelsTest.php b/tests/Feature/AssetModels/Ui/UpdateAssetModelsTest.php
index d0dbb2ffc..6fdd1bbcf 100644
--- a/tests/Feature/AssetModels/Ui/UpdateAssetModelsTest.php
+++ b/tests/Feature/AssetModels/Ui/UpdateAssetModelsTest.php
@@ -24,7 +24,7 @@ class UpdateAssetModelsTest extends TestCase
public function testPageRenders()
{
$this->actingAs(User::factory()->superuser()->create())
- ->get(route('models.edit', AssetModel::factory()->create()->id))
+ ->get(route('models.edit', AssetModel::factory()->create()))
->assertOk();
}
@@ -55,15 +55,15 @@ class UpdateAssetModelsTest extends TestCase
$this->assertTrue(AssetModel::where('name', 'Test Model')->exists());
$response = $this->actingAs(User::factory()->superuser()->create())
- ->from(route('models.edit', ['model' => $model->id]))
- ->put(route('models.update', ['model' => $model]), [
+ ->from(route('models.edit', $model))
+ ->put(route('models.update', $model), [
'name' => 'Test Model Edited',
'category_id' => Category::factory()->forAccessories()->create()->id,
])
->assertSessionHasErrors(['category_type'])
->assertInvalid(['category_type'])
->assertStatus(302)
- ->assertRedirect(route('models.edit', ['model' => $model->id]));
+ ->assertRedirect(route('models.edit', $model));
$this->followRedirects($response)->assertSee(trans('general.error'));
$this->assertFalse(AssetModel::where('name', 'Test Model Edited')->exists());
diff --git a/tests/Feature/Assets/Api/AuditAssetTest.php b/tests/Feature/Assets/Api/AuditAssetTest.php
new file mode 100644
index 000000000..f93a65ae6
--- /dev/null
+++ b/tests/Feature/Assets/Api/AuditAssetTest.php
@@ -0,0 +1,78 @@
+actingAsForApi(User::factory()->auditAssets()->create())
+ ->postJson(route('api.asset.audit', 123456789))
+ ->assertStatusMessageIs('error');
+ }
+
+ public function testRequiresPermissionToAuditAsset()
+ {
+ $asset = Asset::factory()->create();
+ $this->actingAsForApi(User::factory()->create())
+ ->postJson(route('api.asset.audit', $asset))
+ ->assertForbidden();
+ }
+
+ public function testLegacyAssetAuditIsSaved()
+ {
+ $asset = Asset::factory()->create();
+ $this->actingAsForApi(User::factory()->auditAssets()->create())
+ ->postJson(route('api.asset.audit.legacy'), [
+ 'asset_tag' => $asset->asset_tag,
+ 'note' => 'test',
+ ])
+ ->assertStatusMessageIs('success')
+ ->assertJson(
+ [
+ 'messages' =>trans('admin/hardware/message.audit.success'),
+ 'payload' => [
+ 'id' => $asset->id,
+ 'asset_tag' => $asset->asset_tag,
+ 'note' => 'test'
+ ],
+ ])
+ ->assertStatus(200);
+
+ }
+
+
+ public function testAssetAuditIsSaved()
+ {
+ $asset = Asset::factory()->create();
+ $this->actingAsForApi(User::factory()->auditAssets()->create())
+ ->postJson(route('api.asset.audit', $asset), [
+ 'note' => 'test'
+ ])
+ ->assertStatusMessageIs('success')
+ ->assertJson(
+ [
+ 'messages' =>trans('admin/hardware/message.audit.success'),
+ 'payload' => [
+ 'id' => $asset->id,
+ 'asset_tag' => $asset->asset_tag,
+ 'note' => 'test'
+ ],
+ ])
+ ->assertStatus(200);
+
+ }
+
+
+}
diff --git a/tests/Feature/Assets/Ui/AssetLabelTest.php b/tests/Feature/Assets/Ui/AssetLabelTest.php
new file mode 100644
index 000000000..ddf57828b
--- /dev/null
+++ b/tests/Feature/Assets/Ui/AssetLabelTest.php
@@ -0,0 +1,36 @@
+count(20)->create();
+ $id_array = $assets->pluck('id')->toArray();
+
+ $this->actingAs(User::factory()->viewAssets()->create())->post('/hardware/bulkedit', [
+ 'ids' => $id_array,
+ 'bulk_actions' => 'labels',
+ ])->assertStatus(200);
+ }
+
+ public function testRedirectOfNoAssetsSelected()
+ {
+ $id_array = [];
+ $this->actingAs(User::factory()->viewAssets()->create())
+ ->from(route('hardware.index'))
+ ->post('/hardware/bulkedit', [
+ 'ids' => $id_array,
+ 'bulk_actions' => 'Labels',
+ ])->assertStatus(302)
+ ->assertRedirect(route('hardware.index'));
+ }
+
+}
diff --git a/tests/Feature/Assets/Ui/AuditAssetTest.php b/tests/Feature/Assets/Ui/AuditAssetTest.php
new file mode 100644
index 000000000..a966e6f45
--- /dev/null
+++ b/tests/Feature/Assets/Ui/AuditAssetTest.php
@@ -0,0 +1,34 @@
+actingAs(User::factory()->create())
+ ->get(route('clone/hardware', Asset::factory()->create()))
+ ->assertForbidden();
+ }
+
+ public function testPageCanBeAccessed(): void
+ {
+ $this->actingAs(User::factory()->auditAssets()->create())
+ ->get(route('asset.audit.create', Asset::factory()->create()))
+ ->assertStatus(200);
+ }
+
+ public function testAssetCanBeAudited()
+ {
+ $response = $this->actingAs(User::factory()->auditAssets()->create())
+ ->post(route('asset.audit.store', Asset::factory()->create()))
+ ->assertStatus(302)
+ ->assertRedirect(route('assets.audit.due'));
+
+ $this->followRedirects($response)->assertSee('success');
+ }
+}
diff --git a/tests/Feature/Assets/Ui/BulkDeleteAssetsTest.php b/tests/Feature/Assets/Ui/BulkDeleteAssetsTest.php
index d1375c539..38c69f3b9 100644
--- a/tests/Feature/Assets/Ui/BulkDeleteAssetsTest.php
+++ b/tests/Feature/Assets/Ui/BulkDeleteAssetsTest.php
@@ -162,5 +162,28 @@ class BulkDeleteAssetsTest extends TestCase
);
}
+ public function testBulkDeleteAssignedAssetTriggersError(){
+ $user = User::factory()->viewAssets()->deleteAssets()->editAssets()->create();
+ $asset = Asset::factory()->create([
+ 'id' => 5,
+ 'assigned_to' => $user->id,
+ 'asset_tag' => '12345',
+ ]);
+
+ $response = $this->actingAs($user)
+ ->from(route('hardware/bulkedit'))
+ ->post('/hardware/bulkdelete', [
+ 'ids' => [$asset->id],
+ 'bulk_actions' => 'delete',
+ ]);
+
+ $this->assertEquals(302, $response->getStatusCode());
+ $this->assertEquals(route('hardware.index'), $response->headers->get('Location'));
+
+
+ $errorMessage = session('error');
+ $expectedMessage = trans_choice('admin/hardware/message.delete.assigned_to_error',1, ['asset_tag' => $asset->asset_tag]);
+ $this->assertEquals($expectedMessage, $errorMessage);
+ }
}
diff --git a/tests/Feature/Assets/Ui/DeleteAssetTest.php b/tests/Feature/Assets/Ui/DeleteAssetTest.php
new file mode 100644
index 000000000..a12e9e2cd
--- /dev/null
+++ b/tests/Feature/Assets/Ui/DeleteAssetTest.php
@@ -0,0 +1,86 @@
+actingAs(User::factory()->create())
+ ->delete(route('hardware.destroy', Asset::factory()->create()))
+ ->assertForbidden();
+ }
+
+ public function testCanDeleteAsset()
+ {
+ $asset = Asset::factory()->create();
+
+ $this->actingAs(User::factory()->deleteAssets()->create())
+ ->delete(route('hardware.destroy', $asset))
+ ->assertRedirectToRoute('hardware.index')
+ ->assertSessionHas('success');
+
+ $this->assertSoftDeleted($asset);
+ }
+
+ public function testActionLogEntryMadeWhenAssetDeleted()
+ {
+ $actor = User::factory()->deleteAssets()->create();
+
+ $asset = Asset::factory()->create();
+
+ $this->actingAs($actor)->delete(route('hardware.destroy', $asset));
+
+ $this->assertDatabaseHas('action_logs', [
+ 'created_by' => $actor->id,
+ 'action_type' => 'delete',
+ 'target_id' => null,
+ 'target_type' => null,
+ 'item_type' => Asset::class,
+ 'item_id' => $asset->id,
+ ]);
+ }
+
+ public function testAssetIsCheckedInWhenDeleted()
+ {
+ Event::fake();
+
+ $assignedUser = User::factory()->create();
+ $asset = Asset::factory()->assignedToUser($assignedUser)->create();
+
+ $this->assertTrue($assignedUser->assets->contains($asset));
+
+ $this->actingAs(User::factory()->deleteAssets()->create())
+ ->delete(route('hardware.destroy', $asset));
+
+ $this->assertFalse(
+ $assignedUser->fresh()->assets->contains($asset),
+ 'Asset still assigned to user after deletion'
+ );
+
+ Event::assertDispatched(CheckoutableCheckedIn::class);
+ }
+
+ public function testImageIsDeletedWhenAssetDeleted()
+ {
+ Storage::fake('public');
+
+ $asset = Asset::factory()->create(['image' => 'image.jpg']);
+
+ Storage::disk('public')->put('assets/image.jpg', 'content');
+
+ Storage::disk('public')->assertExists('assets/image.jpg');
+
+ $this->actingAs(User::factory()->deleteAssets()->create())
+ ->delete(route('hardware.destroy', $asset));
+
+ Storage::disk('public')->assertMissing('assets/image.jpg');
+ }
+}
diff --git a/tests/Feature/Assets/Ui/EditAssetTest.php b/tests/Feature/Assets/Ui/EditAssetTest.php
index 27f00b531..f443f6640 100644
--- a/tests/Feature/Assets/Ui/EditAssetTest.php
+++ b/tests/Feature/Assets/Ui/EditAssetTest.php
@@ -27,7 +27,7 @@ class EditAssetTest extends TestCase
{
$asset = Asset::factory()->create();
$user = User::factory()->editAssets()->create();
- $response = $this->actingAs($user)->get(route('hardware.edit', $asset->id));
+ $response = $this->actingAs($user)->get(route('hardware.edit', $asset));
$response->assertStatus(200);
}
@@ -63,7 +63,7 @@ class EditAssetTest extends TestCase
'model_id' => AssetModel::factory()->create()->id,
])
->assertStatus(302)
- ->assertRedirect(route('hardware.show', ['hardware' => $asset->id]));
+ ->assertRedirect(route('hardware.show', $asset));
$this->assertDatabaseHas('assets', ['asset_tag' => 'New Asset Tag']);
}
@@ -81,8 +81,8 @@ class EditAssetTest extends TestCase
$currentTimestamp = now();
$this->actingAs(User::factory()->viewAssets()->editAssets()->create())
- ->from(route('hardware.edit', $asset->id))
- ->put(route('hardware.update', $asset->id), [
+ ->from(route('hardware.edit', $asset))
+ ->put(route('hardware.update', $asset), [
'status_id' => $achived_status->id,
'model_id' => $asset->model_id,
'asset_tags' => $asset->asset_tag,
@@ -98,7 +98,7 @@ class EditAssetTest extends TestCase
$this->assertEquals($achived_status->id, $asset->status_id);
Event::assertDispatched(function (CheckoutableCheckedIn $event) use ($currentTimestamp) {
- return Carbon::parse($event->action_date)->diffInSeconds($currentTimestamp) < 2;
+ return (int) Carbon::parse($event->action_date)->diffInSeconds($currentTimestamp, true) < 2;
}, 1);
}
diff --git a/tests/Feature/Assets/Ui/ShowAssetTest.php b/tests/Feature/Assets/Ui/ShowAssetTest.php
new file mode 100644
index 000000000..23b6c8f31
--- /dev/null
+++ b/tests/Feature/Assets/Ui/ShowAssetTest.php
@@ -0,0 +1,26 @@
+create();
+
+ $asset->model_id = null;
+ $asset->forceSave();
+
+ $asset->refresh();
+
+ $this->assertNull($asset->fresh()->model_id, 'This test needs model_id to be null to be helpful.');
+
+ $this->actingAs(User::factory()->superuser()->create())
+ ->get(route('hardware.show', $asset))
+ ->assertOk();
+ }
+}
diff --git a/tests/Feature/Authentication/LoginTest.php b/tests/Feature/Authentication/LoginTest.php
new file mode 100644
index 000000000..2f688a59d
--- /dev/null
+++ b/tests/Feature/Authentication/LoginTest.php
@@ -0,0 +1,82 @@
+create(['username' => 'username_here']);
+
+ $this->withServerVariables(['REMOTE_ADDR' => '127.0.0.100'])
+ ->post('/login', [
+ 'username' => 'username_here',
+ 'password' => 'not a real password',
+ ], [
+ 'User-Agent' => 'Some Custom User Agent',
+ ]);
+
+ $this->assertDatabaseHas('login_attempts', [
+ 'username' => 'username_here',
+ 'remote_ip' => '127.0.0.100',
+ 'user_agent' => 'Some Custom User Agent',
+ 'successful' => 0,
+ ]);
+ }
+
+
+ public function testLoginThrottleConfigIsRespected()
+ {
+
+ User::factory()->create(['username' => 'username_here']);
+
+ config(['auth.passwords.users.throttle.max_attempts' => 1]);
+ config(['auth.passwords.users.throttle.lockout_duration' => 1]);
+
+ for ($i = 0; $i < 2; ++$i) {
+ $this->from('/login')
+ ->withServerVariables(['REMOTE_ADDR' => '127.0.0.100'])
+ ->post('/login', [
+ 'username' => 'invalid username',
+ 'password' => 'invalid password',
+ ]);
+ }
+
+
+ $response = $this->from('/login')
+ ->withServerVariables(['REMOTE_ADDR' => '127.0.0.100'])
+ ->post('/login', [
+ 'username' => 'invalid username',
+ 'password' => 'invalid password',
+ ])
+ ->assertSessionHasErrors(['username'])
+ ->assertStatus(302)
+ ->assertRedirect('/login');
+
+ $this->followRedirects($response)->assertSee(trans('auth.throttle', ['minutes' => 1]));
+ }
+
+ public function testLogsSuccessfulLogin()
+ {
+ User::factory()->create(['username' => 'username_here']);
+
+ $this->withServerVariables(['REMOTE_ADDR' => '127.0.0.100'])
+ ->post('/login', [
+ 'username' => 'username_here',
+ 'password' => 'password',
+ ], [
+ 'User-Agent' => 'Some Custom User Agent',
+ ]);
+
+ $this->assertDatabaseHas('login_attempts', [
+ 'username' => 'username_here',
+ 'remote_ip' => '127.0.0.100',
+ 'user_agent' => 'Some Custom User Agent',
+ 'successful' => 1,
+ ]);
+ }
+}
diff --git a/tests/Feature/Categories/Api/CreateCategoriesTest.php b/tests/Feature/Categories/Api/CreateCategoriesTest.php
index fc464242a..2a4a0661a 100644
--- a/tests/Feature/Categories/Api/CreateCategoriesTest.php
+++ b/tests/Feature/Categories/Api/CreateCategoriesTest.php
@@ -27,6 +27,7 @@ class CreateCategoriesTest extends TestCase
'name' => 'Test Category',
'eula_text' => 'Test EULA',
'category_type' => 'accessory',
+ 'notes' => 'Test Note',
])
->assertOk()
->assertStatusMessageIs('success')
@@ -38,6 +39,7 @@ class CreateCategoriesTest extends TestCase
$category = Category::find($response['payload']['id']);
$this->assertEquals('Test Category', $category->name);
$this->assertEquals('Test EULA', $category->eula_text);
+ $this->assertEquals('Test Note', $category->notes);
$this->assertEquals('accessory', $category->category_type);
}
diff --git a/tests/Feature/Categories/Api/UpdateCategoriesTest.php b/tests/Feature/Categories/Api/UpdateCategoriesTest.php
index 1a784c117..6d6bc8da1 100644
--- a/tests/Feature/Categories/Api/UpdateCategoriesTest.php
+++ b/tests/Feature/Categories/Api/UpdateCategoriesTest.php
@@ -17,6 +17,7 @@ class UpdateCategoriesTest extends TestCase
->patchJson(route('api.categories.update', $category), [
'name' => 'Test Category',
'eula_text' => 'Test EULA',
+ 'notes' => 'Test Note',
])
->assertOk()
->assertStatusMessageIs('success')
@@ -27,6 +28,7 @@ class UpdateCategoriesTest extends TestCase
$category->refresh();
$this->assertEquals('Test Category', $category->name, 'Name was not updated');
$this->assertEquals('Test EULA', $category->eula_text, 'EULA was not updated');
+ $this->assertEquals('Test Note', $category->notes, 'Note was not updated');
}
@@ -39,6 +41,7 @@ class UpdateCategoriesTest extends TestCase
'name' => 'Test Category',
'eula_text' => 'Test EULA',
'category_type' => 'accessory',
+ 'note' => 'Test Note',
])
->assertOk()
->assertStatusMessageIs('error')
@@ -48,6 +51,7 @@ class UpdateCategoriesTest extends TestCase
$category->refresh();
$this->assertNotEquals('Test Category', $category->name, 'Name was not updated');
$this->assertNotEquals('Test EULA', $category->eula_text, 'EULA was not updated');
+ $this->assertNotEquals('Test Note', $category->notes, 'Note was not updated');
$this->assertNotEquals('accessory', $category->category_type, 'EULA was not updated');
}
diff --git a/tests/Feature/Categories/Ui/CreateCategoriesTest.php b/tests/Feature/Categories/Ui/CreateCategoriesTest.php
index 45e821d9d..694b61c61 100644
--- a/tests/Feature/Categories/Ui/CreateCategoriesTest.php
+++ b/tests/Feature/Categories/Ui/CreateCategoriesTest.php
@@ -33,11 +33,12 @@ class CreateCategoriesTest extends TestCase
$this->actingAs(User::factory()->superuser()->create())
->post(route('categories.store'), [
'name' => 'Test Category',
- 'category_type' => 'asset'
+ 'category_type' => 'asset',
+ 'notes' => 'Test Note',
])
->assertRedirect(route('categories.index'));
- $this->assertTrue(Category::where('name', 'Test Category')->exists());
+ $this->assertTrue(Category::where('name', 'Test Category')->where('notes', 'Test Note')->exists());
}
public function testUserCannotCreateCategoriesWithInvalidType()
@@ -48,7 +49,7 @@ class CreateCategoriesTest extends TestCase
->from(route('categories.create'))
->post(route('categories.store'), [
'name' => 'Test Category',
- 'category_type' => 'invalid'
+ 'category_type' => 'invalid',
])
->assertRedirect(route('categories.create'));
diff --git a/tests/Feature/Categories/Ui/ShowCategoryTest.php b/tests/Feature/Categories/Ui/ShowCategoryTest.php
index dd22b1d75..93a9cc0af 100644
--- a/tests/Feature/Categories/Ui/ShowCategoryTest.php
+++ b/tests/Feature/Categories/Ui/ShowCategoryTest.php
@@ -11,7 +11,7 @@ class ShowCategoryTest extends TestCase
public function testPageRenders()
{
$this->actingAs(User::factory()->superuser()->create())
- ->get(route('categories.show', Category::factory()->create()->id))
+ ->get(route('categories.show', Category::factory()->create()))
->assertOk();
}
}
diff --git a/tests/Feature/Categories/Ui/UpdateCategoriesTest.php b/tests/Feature/Categories/Ui/UpdateCategoriesTest.php
index 901b77958..ea5cc6338 100644
--- a/tests/Feature/Categories/Ui/UpdateCategoriesTest.php
+++ b/tests/Feature/Categories/Ui/UpdateCategoriesTest.php
@@ -23,7 +23,7 @@ class UpdateCategoriesTest extends TestCase
public function testPageRenders()
{
$this->actingAs(User::factory()->superuser()->create())
- ->get(route('categories.edit', Category::factory()->create()->id))
+ ->get(route('categories.edit', Category::factory()->create()))
->assertOk();
}
@@ -32,7 +32,7 @@ class UpdateCategoriesTest extends TestCase
$this->actingAs(User::factory()->superuser()->create())
->post(route('categories.store'), [
'name' => 'Test Category',
- 'category_type' => 'asset'
+ 'category_type' => 'asset',
])
->assertStatus(302)
->assertSessionHasNoErrors()
@@ -47,15 +47,16 @@ class UpdateCategoriesTest extends TestCase
$this->assertTrue(Category::where('name', 'Test Category')->exists());
$response = $this->actingAs(User::factory()->superuser()->create())
- ->put(route('categories.update', ['category' => $category]), [
+ ->put(route('categories.update', $category), [
'name' => 'Test Category Edited',
+ 'notes' => 'Test Note Edited',
])
->assertStatus(302)
->assertSessionHasNoErrors()
->assertRedirect(route('categories.index'));
$this->followRedirects($response)->assertSee('Success');
- $this->assertTrue(Category::where('name', 'Test Category Edited')->exists());
+ $this->assertTrue(Category::where('name', 'Test Category Edited')->where('notes', 'Test Note Edited')->exists());
}
@@ -65,17 +66,18 @@ class UpdateCategoriesTest extends TestCase
$this->assertTrue(Category::where('name', 'Test Category')->exists());
$response = $this->actingAs(User::factory()->superuser()->create())
- ->from(route('categories.edit', ['category' => $category->id]))
- ->put(route('categories.update', ['category' => $category]), [
+ ->from(route('categories.edit', $category->id))
+ ->put(route('categories.update', $category), [
'name' => 'Test Category Edited',
'category_type' => 'accessory',
+ 'notes' => 'Test Note Edited',
])
->assertSessionHasNoErrors()
->assertStatus(302)
->assertRedirect(route('categories.index'));
$this->followRedirects($response)->assertSee('Success');
- $this->assertTrue(Category::where('name', 'Test Category Edited')->exists());
+ $this->assertTrue(Category::where('name', 'Test Category Edited')->where('notes', 'Test Note Edited')->exists());
}
@@ -85,18 +87,19 @@ class UpdateCategoriesTest extends TestCase
$category = Category::where('name', 'Laptops')->first();
$response = $this->actingAs(User::factory()->superuser()->create())
- ->from(route('categories.edit', ['category' => $category->id]))
- ->put(route('categories.update', ['category' => $category]), [
+ ->from(route('categories.edit', $category))
+ ->put(route('categories.update', $category), [
'name' => 'Test Category Edited',
'category_type' => 'accessory',
+ 'notes' => 'Test Note Edited',
])
->assertSessionHasErrors(['category_type'])
->assertInvalid(['category_type'])
->assertStatus(302)
- ->assertRedirect(route('categories.edit', ['category' => $category->id]));
+ ->assertRedirect(route('categories.edit', $category));
$this->followRedirects($response)->assertSee(trans('general.error'));
- $this->assertFalse(Category::where('name', 'Test Category Edited')->exists());
+ $this->assertFalse(Category::where('name', 'Test Category Edited')->where('notes', 'Test Note Edited')->exists());
}
diff --git a/tests/Feature/Checkins/Api/AssetCheckinTest.php b/tests/Feature/Checkins/Api/AssetCheckinTest.php
index 0e041fc73..7ce3d0561 100644
--- a/tests/Feature/Checkins/Api/AssetCheckinTest.php
+++ b/tests/Feature/Checkins/Api/AssetCheckinTest.php
@@ -72,7 +72,7 @@ class AssetCheckinTest extends TestCase
Event::assertDispatched(function (CheckoutableCheckedIn $event) use ($currentTimestamp) {
// this could be better mocked but is ok for now.
- return Carbon::parse($event->action_date)->diffInSeconds($currentTimestamp) < 2;
+ return (int) Carbon::parse($event->action_date)->diffInSeconds($currentTimestamp, true) < 2;
}, 1);
}
diff --git a/tests/Feature/Checkins/Api/LicenseCheckInTest.php b/tests/Feature/Checkins/Api/LicenseCheckInTest.php
new file mode 100644
index 000000000..385933655
--- /dev/null
+++ b/tests/Feature/Checkins/Api/LicenseCheckInTest.php
@@ -0,0 +1,45 @@
+superuser()->create();
+ $this->actingAsForApi($authUser);
+
+ $license = License::factory()->create();
+ $oldUser = User::factory()->create();
+
+ $licenseSeat = LicenseSeat::factory()->for($license)->create([
+ 'assigned_to' => $oldUser->id,
+ 'notes' => 'Previously checked out',
+ ]);
+
+ $payload = [
+ 'assigned_to' => null,
+ 'asset_id' => null,
+ 'notes' => 'Checking in the seat',
+ ];
+
+ $response = $this->patchJson(
+ route('api.licenses.seats.update', [$license->id, $licenseSeat->id]),
+ $payload);
+
+ $response->assertStatus(200)
+ ->assertJsonFragment([
+ 'status' => 'success',
+ ]);
+
+ $licenseSeat->refresh();
+
+ $this->assertNull($licenseSeat->assigned_to);
+ $this->assertNull($licenseSeat->asset_id);
+
+ $this->assertEquals('Checking in the seat', $licenseSeat->notes);
+ }
+}
\ No newline at end of file
diff --git a/tests/Feature/Checkins/Ui/AssetCheckinTest.php b/tests/Feature/Checkins/Ui/AssetCheckinTest.php
index 7428b7ab7..4f1c62cf2 100644
--- a/tests/Feature/Checkins/Ui/AssetCheckinTest.php
+++ b/tests/Feature/Checkins/Ui/AssetCheckinTest.php
@@ -18,16 +18,14 @@ class AssetCheckinTest extends TestCase
public function testCheckingInAssetRequiresCorrectPermission()
{
$this->actingAs(User::factory()->create())
- ->post(route('hardware.checkin.store', [
- 'assetId' => Asset::factory()->assignedToUser()->create()->id,
- ]))
+ ->post(route('hardware.checkin.store', [Asset::factory()->assignedToUser()->create()]))
->assertForbidden();
}
public function testCannotCheckInAssetThatIsNotCheckedOut()
{
$this->actingAs(User::factory()->checkinAssets()->create())
- ->post(route('hardware.checkin.store', ['assetId' => Asset::factory()->create()->id]))
+ ->post(route('hardware.checkin.store', [Asset::factory()->create()]))
->assertStatus(302)
->assertSessionHas('error')
->assertRedirect(route('hardware.index'));
@@ -36,7 +34,7 @@ class AssetCheckinTest extends TestCase
public function testCannotStoreAssetCheckinThatIsNotCheckedOut()
{
$this->actingAs(User::factory()->checkinAssets()->create())
- ->get(route('hardware.checkin.store', ['assetId' => Asset::factory()->create()->id]))
+ ->get(route('hardware.checkin.store', [Asset::factory()->create()]))
->assertStatus(302)
->assertSessionHas('error')
->assertRedirect(route('hardware.index'));
@@ -68,7 +66,7 @@ class AssetCheckinTest extends TestCase
$this->actingAs(User::factory()->checkinAssets()->create())
->post(
- route('hardware.checkin.store', ['assetId' => $asset->id]),
+ route('hardware.checkin.store', [$asset]),
[
'name' => 'Changed Name',
'status_id' => $status->id,
@@ -88,7 +86,7 @@ class AssetCheckinTest extends TestCase
Event::assertDispatched(function (CheckoutableCheckedIn $event) use ($currentTimestamp) {
// this could be better mocked but is ok for now.
- return Carbon::parse($event->action_date)->diffInSeconds($currentTimestamp) < 2;
+ return (int) Carbon::parse($event->action_date)->diffInSeconds($currentTimestamp, true) < 2;
}, 1);
}
@@ -101,7 +99,7 @@ class AssetCheckinTest extends TestCase
]);
$this->actingAs(User::factory()->checkinAssets()->create())
- ->post(route('hardware.checkin.store', ['assetId' => $asset->id]));
+ ->post(route('hardware.checkin.store', [$asset]));
$this->assertTrue($asset->refresh()->location()->is($rtdLocation));
}
@@ -112,7 +110,7 @@ class AssetCheckinTest extends TestCase
$asset = Asset::factory()->assignedToUser()->create();
$this->actingAs(User::factory()->checkinAssets()->create())
- ->post(route('hardware.checkin.store', ['assetId' => $asset->id]), [
+ ->post(route('hardware.checkin.store', [$asset]), [
'location_id' => $location->id,
'update_default_location' => 0
]);
@@ -128,7 +126,7 @@ class AssetCheckinTest extends TestCase
$this->assertNotNull($asset->licenseseats->first()->assigned_to);
$this->actingAs(User::factory()->checkinAssets()->create())
- ->post(route('hardware.checkin.store', ['assetId' => $asset->id]));
+ ->post(route('hardware.checkin.store', [$asset]));
$this->assertNull($asset->refresh()->licenseseats->first()->assigned_to);
}
@@ -141,7 +139,7 @@ class AssetCheckinTest extends TestCase
]);
$this->actingAs(User::factory()->checkinAssets()->create())
- ->post(route('hardware.checkin.store', ['assetId' => $asset->id]));
+ ->post(route('hardware.checkin.store', [$asset]));
$this->assertNull($asset->refresh()->rtd_location_id);
$this->assertEquals($asset->location_id, $asset->rtd_location_id);
@@ -154,7 +152,7 @@ class AssetCheckinTest extends TestCase
$acceptance = CheckoutAcceptance::factory()->for($asset, 'checkoutable')->pending()->create();
$this->actingAs(User::factory()->checkinAssets()->create())
- ->post(route('hardware.checkin.store', ['assetId' => $asset->id]));
+ ->post(route('hardware.checkin.store', [$asset]));
$this->assertFalse($acceptance->exists(), 'Acceptance was not deleted');
}
@@ -165,8 +163,7 @@ class AssetCheckinTest extends TestCase
$this->actingAs(User::factory()->checkinAssets()->create())
->post(route(
- 'hardware.checkin.store',
- ['assetId' => Asset::factory()->assignedToUser()->create()->id]
+ 'hardware.checkin.store', [Asset::factory()->assignedToUser()->create()]
), [
'checkin_at' => '2023-01-02',
'note' => 'hello'
@@ -185,10 +182,10 @@ class AssetCheckinTest extends TestCase
$asset->forceSave();
$this->actingAs(User::factory()->admin()->create())
- ->get(route('hardware.checkin.create', ['assetId' => $asset->id]))
+ ->get(route('hardware.checkin.create', [$asset]))
->assertStatus(302)
->assertSessionHas('error')
- ->assertRedirect(route('hardware.show',['hardware' => $asset->id]));
+ ->assertRedirect(route('hardware.show', $asset));
}
public function testAssetCheckinPagePostIsRedirectedIfModelIsInvalid()
@@ -198,10 +195,10 @@ class AssetCheckinTest extends TestCase
$asset->forceSave();
$this->actingAs(User::factory()->admin()->create())
- ->post(route('hardware.checkin.store', ['assetId' => $asset->id]))
+ ->post(route('hardware.checkin.store', $asset))
->assertStatus(302)
->assertSessionHas('error')
- ->assertRedirect(route('hardware.show', ['hardware' => $asset->id]));
+ ->assertRedirect(route('hardware.show', $asset));
}
public function testAssetCheckinPagePostIsRedirectedIfRedirectSelectionIsIndex()
@@ -228,6 +225,6 @@ class AssetCheckinTest extends TestCase
])
->assertStatus(302)
->assertSessionHasNoErrors()
- ->assertRedirect(route('hardware.show', ['hardware' => $asset->id]));
+ ->assertRedirect(route('hardware.show', $asset));
}
}
diff --git a/tests/Feature/Checkins/Ui/ComponentCheckinTest.php b/tests/Feature/Checkins/Ui/ComponentCheckinTest.php
index 6c2ef084f..7da3879ea 100644
--- a/tests/Feature/Checkins/Ui/ComponentCheckinTest.php
+++ b/tests/Feature/Checkins/Ui/ComponentCheckinTest.php
@@ -16,9 +16,7 @@ class ComponentCheckinTest extends TestCase
$componentAsset = DB::table('components_assets')->where('component_id', $component->id)->first();
$this->actingAs(User::factory()->create())
- ->post(route('components.checkin.store', [
- 'componentID' => $componentAsset->id,
- ]))
+ ->post(route('components.checkin.store', $componentAsset->id))
->assertForbidden();
}
@@ -67,6 +65,6 @@ class ComponentCheckinTest extends TestCase
])
->assertStatus(302)
->assertSessionHasNoErrors()
- ->assertRedirect(route('components.show', ['component' => $component->id]));
+ ->assertRedirect(route('components.show', $component));
}
}
diff --git a/tests/Feature/Checkouts/Api/AssetCheckoutTest.php b/tests/Feature/Checkouts/Api/AssetCheckoutTest.php
index ded388964..e83ad7d30 100644
--- a/tests/Feature/Checkouts/Api/AssetCheckoutTest.php
+++ b/tests/Feature/Checkouts/Api/AssetCheckoutTest.php
@@ -2,6 +2,8 @@
namespace Tests\Feature\Checkouts\Api;
+use Illuminate\Support\Facades\Mail;
+use Notification;
use PHPUnit\Framework\Attributes\DataProvider;
use App\Events\CheckoutableCheckedOut;
use App\Models\Asset;
@@ -21,6 +23,22 @@ class AssetCheckoutTest extends TestCase
Event::fake([CheckoutableCheckedOut::class]);
}
+ public function testCheckoutRequest()
+ {
+ Notification::fake();
+ $requestable = Asset::factory()->requestable()->create();
+ $nonRequestable = Asset::factory()->nonrequestable()->create();
+
+ $this->actingAsForApi(User::factory()->create())
+ ->post(route('api.assets.requests.store', $requestable->id))
+ ->assertStatusMessageIs('success');
+
+ $this->actingAsForApi(User::factory()->create())
+ ->post(route('api.assets.requests.store', $nonRequestable->id))
+ ->assertStatusMessageIs('error');
+
+ }
+
public function testCheckingOutAssetRequiresCorrectPermission()
{
$this->actingAsForApi(User::factory()->create())
@@ -209,6 +227,6 @@ class AssetCheckoutTest extends TestCase
$asset->refresh();
- $this->assertTrue(Carbon::parse($asset->last_checkout)->diffInSeconds(now()) < 2);
+ $this->assertTrue((int) Carbon::parse($asset->last_checkout)->diffInSeconds(now(), true) < 2);
}
}
diff --git a/tests/Feature/Checkouts/Api/LicenseCheckOutTest.php b/tests/Feature/Checkouts/Api/LicenseCheckOutTest.php
new file mode 100644
index 000000000..7a069b7d2
--- /dev/null
+++ b/tests/Feature/Checkouts/Api/LicenseCheckOutTest.php
@@ -0,0 +1,41 @@
+superuser()->create();
+ $this->actingAsForApi($authUser);
+
+ $license = License::factory()->create();
+ $licenseSeat = LicenseSeat::factory()->for($license)->create([
+ 'assigned_to' => null,
+ ]);
+
+ $targetUser = User::factory()->create();
+
+ $payload = [
+ 'assigned_to' => $targetUser->id,
+ 'notes' => 'Checking out the seat to a user',
+ ];
+
+ $response = $this->patchJson(
+ route('api.licenses.seats.update', [$license->id, $licenseSeat->id]),
+ $payload);
+
+ $response->assertStatus(200)
+ ->assertJsonFragment([
+ 'status' => 'success',
+ ]);
+
+ $licenseSeat->refresh();
+
+ $this->assertEquals($targetUser->id, $licenseSeat->assigned_to);
+ $this->assertEquals('Checking out the seat to a user', $licenseSeat->notes);
+ }
+}
\ No newline at end of file
diff --git a/tests/Feature/Checkouts/Ui/AccessoryCheckoutTest.php b/tests/Feature/Checkouts/Ui/AccessoryCheckoutTest.php
index ff121bc4d..19b0d1ce7 100644
--- a/tests/Feature/Checkouts/Ui/AccessoryCheckoutTest.php
+++ b/tests/Feature/Checkouts/Ui/AccessoryCheckoutTest.php
@@ -25,7 +25,7 @@ class AccessoryCheckoutTest extends TestCase
public function testPageRenders()
{
$this->actingAs(User::factory()->superuser()->create())
- ->get(route('accessories.checkout.show', Accessory::factory()->create()->id))
+ ->get(route('accessories.checkout.show', Accessory::factory()->create()))
->assertOk();
}
@@ -241,7 +241,7 @@ class AccessoryCheckoutTest extends TestCase
])
->assertStatus(302)
->assertSessionHasNoErrors()
- ->assertRedirect(route('accessories.show', ['accessory' => $accessory->id]));
+ ->assertRedirect(route('accessories.show', $accessory));
}
public function testAccessoryCheckoutPagePostIsRedirectedIfRedirectSelectionIsTarget()
@@ -258,6 +258,6 @@ class AccessoryCheckoutTest extends TestCase
'assigned_qty' => 1,
])
->assertStatus(302)
- ->assertRedirect(route('users.show', ['user' => $user]));
+ ->assertRedirect(route('users.show', $user));
}
}
diff --git a/tests/Feature/Checkouts/Ui/AssetCheckoutTest.php b/tests/Feature/Checkouts/Ui/AssetCheckoutTest.php
index 74658a6fa..001979175 100644
--- a/tests/Feature/Checkouts/Ui/AssetCheckoutTest.php
+++ b/tests/Feature/Checkouts/Ui/AssetCheckoutTest.php
@@ -249,7 +249,7 @@ class AssetCheckoutTest extends TestCase
$asset->refresh();
- $this->assertTrue(Carbon::parse($asset->last_checkout)->diffInSeconds(now()) < 2);
+ $this->assertTrue((int) Carbon::parse($asset->last_checkout)->diffInSeconds(now(), true) < 2);
}
public function testAssetCheckoutPageIsRedirectedIfModelIsInvalid()
@@ -260,10 +260,10 @@ class AssetCheckoutTest extends TestCase
$asset->forceSave();
$this->actingAs(User::factory()->admin()->create())
- ->get(route('hardware.checkout.create', ['assetId' => $asset->id]))
+ ->get(route('hardware.checkout.create', $asset))
->assertStatus(302)
->assertSessionHas('error')
- ->assertRedirect(route('hardware.show',['hardware' => $asset->id]));
+ ->assertRedirect(route('hardware.show', $asset));
}
public function testAssetCheckoutPagePostIsRedirectedIfRedirectSelectionIsIndex()
@@ -294,7 +294,7 @@ class AssetCheckoutTest extends TestCase
])
->assertStatus(302)
->assertSessionHasNoErrors()
- ->assertRedirect(route('hardware.show', ['hardware' => $asset->id]));
+ ->assertRedirect(route('hardware.show', $asset));
}
public function testAssetCheckoutPagePostIsRedirectedIfRedirectSelectionIsUserTarget()
@@ -328,7 +328,7 @@ class AssetCheckoutTest extends TestCase
'assigned_qty' => 1,
])
->assertStatus(302)
- ->assertRedirect(route('hardware.show', ['hardware' => $target]));
+ ->assertRedirect(route('hardware.show', $target));
}
public function testAssetCheckoutPagePostIsRedirectedIfRedirectSelectionIsLocationTarget()
diff --git a/tests/Feature/Checkouts/Ui/BulkAssetCheckoutTest.php b/tests/Feature/Checkouts/Ui/BulkAssetCheckoutTest.php
new file mode 100644
index 000000000..08d6cf684
--- /dev/null
+++ b/tests/Feature/Checkouts/Ui/BulkAssetCheckoutTest.php
@@ -0,0 +1,91 @@
+actingAs(User::factory()->create())
+ ->post(route('hardware.bulkcheckout.store'), [
+ 'selected_assets' => [1],
+ 'checkout_to_type' => 'user',
+ 'assigned_user' => 1,
+ 'assigned_asset' => null,
+ 'checkout_at' => null,
+ 'expected_checkin' => null,
+ 'note' => null,
+ ])
+ ->assertForbidden();
+ }
+
+ public function testCanBulkCheckoutAssets()
+ {
+ Mail::fake();
+
+ $assets = Asset::factory()->requiresAcceptance()->count(2)->create();
+ $user = User::factory()->create(['email' => 'someone@example.com']);
+
+ $checkoutAt = now()->subWeek()->format('Y-m-d');
+ $expectedCheckin = now()->addWeek()->format('Y-m-d');
+
+ $this->actingAs(User::factory()->checkoutAssets()->viewAssets()->create())
+ ->followingRedirects()
+ ->post(route('hardware.bulkcheckout.store'), [
+ 'selected_assets' => $assets->pluck('id')->toArray(),
+ 'checkout_to_type' => 'user',
+ 'assigned_user' => $user->id,
+ 'assigned_asset' => null,
+ 'checkout_at' => $checkoutAt,
+ 'expected_checkin' => $expectedCheckin,
+ 'note' => null,
+ ])
+ ->assertOk();
+
+ $assets = $assets->fresh();
+
+ $assets->each(function ($asset) use ($expectedCheckin, $checkoutAt, $user) {
+ $asset->assignedTo()->is($user);
+ $asset->last_checkout = $checkoutAt;
+ $asset->expected_checkin = $expectedCheckin;
+ });
+
+ Mail::assertSent(CheckoutAssetMail::class, 2);
+ Mail::assertSent(CheckoutAssetMail::class, function (CheckoutAssetMail $mail) {
+ return $mail->hasTo('someone@example.com');
+ });
+ }
+
+ public function testHandleMissingModelBeingIncluded()
+ {
+ Mail::fake();
+
+ $this->actingAs(User::factory()->checkoutAssets()->create())
+ ->post(route('hardware.bulkcheckout.store'), [
+ 'selected_assets' => [
+ Asset::factory()->requiresAcceptance()->create()->id,
+ 9999999,
+ ],
+ 'checkout_to_type' => 'user',
+ 'assigned_user' => User::factory()->create(['email' => 'someone@example.com'])->id,
+ 'assigned_asset' => null,
+ 'checkout_at' => null,
+ 'expected_checkin' => null,
+ 'note' => null,
+ ])
+ ->assertSessionHas('error', trans_choice('admin/hardware/message.multi-checkout.error', 2));
+
+ try {
+ Mail::assertNotSent(CheckoutAssetMail::class);
+ } catch (ExpectationFailedException $e) {
+ $this->fail('Asset checkout email was sent when the entire checkout failed.');
+ }
+ }
+}
diff --git a/tests/Feature/Checkouts/Ui/ComponentsCheckoutTest.php b/tests/Feature/Checkouts/Ui/ComponentsCheckoutTest.php
index 507b26243..18568d198 100644
--- a/tests/Feature/Checkouts/Ui/ComponentsCheckoutTest.php
+++ b/tests/Feature/Checkouts/Ui/ComponentsCheckoutTest.php
@@ -76,7 +76,7 @@ class ComponentsCheckoutTest extends TestCase
'assigned_qty' => 1,
])
->assertStatus(302)
- ->assertRedirect(route('components.show', ['component' => $component->id]));
+ ->assertRedirect(route('components.show', $component));
}
public function testComponentCheckoutPagePostIsRedirectedIfRedirectSelectionIsTarget()
@@ -92,6 +92,6 @@ class ComponentsCheckoutTest extends TestCase
'assigned_qty' => 1,
])
->assertStatus(302)
- ->assertRedirect(route('hardware.show', ['hardware' => $asset]));
+ ->assertRedirect(route('hardware.show', $asset));
}
}
diff --git a/tests/Feature/Checkouts/Ui/ConsumableCheckoutTest.php b/tests/Feature/Checkouts/Ui/ConsumableCheckoutTest.php
index dd77e19c5..2c74e4d0c 100644
--- a/tests/Feature/Checkouts/Ui/ConsumableCheckoutTest.php
+++ b/tests/Feature/Checkouts/Ui/ConsumableCheckoutTest.php
@@ -130,7 +130,7 @@ class ConsumableCheckoutTest extends TestCase
'assigned_qty' => 1,
])
->assertStatus(302)
- ->assertRedirect(route('consumables.show', ['consumable' => $consumable->id]));
+ ->assertRedirect(route('consumables.show', $consumable));
}
public function testConsumableCheckoutPagePostIsRedirectedIfRedirectSelectionIsTarget()
@@ -146,7 +146,7 @@ class ConsumableCheckoutTest extends TestCase
'assigned_qty' => 1,
])
->assertStatus(302)
- ->assertRedirect(route('users.show', ['user' => $user]));
+ ->assertRedirect(route('users.show', $user));
}
}
diff --git a/tests/Feature/Checkouts/Ui/LicenseCheckoutTest.php b/tests/Feature/Checkouts/Ui/LicenseCheckoutTest.php
index 22bdb6b5d..1750b7564 100644
--- a/tests/Feature/Checkouts/Ui/LicenseCheckoutTest.php
+++ b/tests/Feature/Checkouts/Ui/LicenseCheckoutTest.php
@@ -24,7 +24,7 @@ class LicenseCheckoutTest extends TestCase
$licenseSeat = LicenseSeat::factory()->create();
$this->actingAs($admin)
- ->post("/licenses/{$licenseSeat->license->id}/checkout", [
+ ->post(route('licenses.checkout', $licenseSeat->license), [
'checkout_to_type' => 'asset',
'assigned_to' => null,
'asset_id' => $asset->id,
@@ -47,7 +47,7 @@ class LicenseCheckoutTest extends TestCase
$licenseSeat = LicenseSeat::factory()->create();
$this->actingAs($admin)
- ->post("/licenses/{$licenseSeat->license->id}/checkout", [
+ ->post(route('licenses.checkout', $licenseSeat->license), [
'checkout_to_type' => 'user',
'assigned_to' => $admin->id,
'asset_id' => null,
@@ -69,8 +69,8 @@ class LicenseCheckoutTest extends TestCase
$license = License::factory()->create();
$this->actingAs(User::factory()->admin()->create())
- ->from(route('licenses.checkout', ['licenseId' => $license->id]))
- ->post(route('licenses.checkout', ['licenseId' => $license->id]), [
+ ->from(route('licenses.checkout', $license))
+ ->post(route('licenses.checkout', $license), [
'assigned_to' => User::factory()->create()->id,
'redirect_option' => 'index',
'assigned_qty' => 1,
@@ -84,13 +84,13 @@ class LicenseCheckoutTest extends TestCase
$license = License::factory()->create();
$this->actingAs(User::factory()->admin()->create())
- ->from(route('licenses.checkout', ['licenseId' => $license->id]))
- ->post(route('licenses.checkout' , ['licenseId' => $license->id]), [
+ ->from(route('licenses.checkout', $license))
+ ->post(route('licenses.checkout', $license), [
'assigned_to' => User::factory()->create()->id,
'redirect_option' => 'item',
])
->assertStatus(302)
- ->assertRedirect(route('licenses.show', ['license' => $license->id]));
+ ->assertRedirect(route('licenses.show', $license));
}
public function testLicenseCheckoutPagePostIsRedirectedIfRedirectSelectionIsUserTarget()
@@ -99,13 +99,13 @@ class LicenseCheckoutTest extends TestCase
$license = License::factory()->create();
$this->actingAs(User::factory()->admin()->create())
- ->from(route('licenses.checkout', ['licenseId' => $license->id]))
+ ->from(route('licenses.checkout', $license))
->post(route('licenses.checkout' , $license), [
'assigned_to' => $user->id,
'redirect_option' => 'target',
])
->assertStatus(302)
- ->assertRedirect(route('users.show', ['user' => $user->id]));
+ ->assertRedirect(route('users.show', $user));
}
public function testLicenseCheckoutPagePostIsRedirectedIfRedirectSelectionIsAssetTarget()
{
@@ -113,12 +113,12 @@ class LicenseCheckoutTest extends TestCase
$license = License::factory()->create();
$this->actingAs(User::factory()->admin()->create())
- ->from(route('licenses.checkout', ['licenseId' => $license->id]))
+ ->from(route('licenses.checkout', $license))
->post(route('licenses.checkout' , $license), [
'asset_id' => $asset->id,
'redirect_option' => 'target',
])
->assertStatus(302)
- ->assertRedirect(route('hardware.show', ['hardware' => $asset->id]));
+ ->assertRedirect(route('hardware.show', $asset));
}
}
diff --git a/tests/Feature/Companies/Api/CreateCompaniesTest.php b/tests/Feature/Companies/Api/CreateCompaniesTest.php
new file mode 100644
index 000000000..3c7c1f4e0
--- /dev/null
+++ b/tests/Feature/Companies/Api/CreateCompaniesTest.php
@@ -0,0 +1,46 @@
+actingAsForApi(User::factory()->create())
+ ->postJson(route('api.companies.store'))
+ ->assertForbidden();
+ }
+
+ public function testValidationForCreatingCompany()
+ {
+ $this->actingAsForApi(User::factory()->createCompanies()->create())
+ ->postJson(route('api.companies.store'))
+ ->assertStatus(200)
+ ->assertStatusMessageIs('error')
+ ->assertJsonStructure([
+ 'messages' => [
+ 'name',
+ ],
+ ]);
+ }
+
+ public function testCanCreateCompany()
+ {
+ $this->actingAsForApi(User::factory()->createCompanies()->create())
+ ->postJson(route('api.companies.store'), [
+ 'name' => 'My Cool Company',
+ 'notes' => 'A Cool Note',
+ ])
+ ->assertStatus(200)
+ ->assertStatusMessageIs('success');
+
+ $this->assertDatabaseHas('companies', [
+ 'name' => 'My Cool Company',
+ 'notes' => 'A Cool Note',
+ ]);
+ }
+}
diff --git a/tests/Feature/Companies/Api/UpdateCompaniesTest.php b/tests/Feature/Companies/Api/UpdateCompaniesTest.php
new file mode 100644
index 000000000..07a7e4211
--- /dev/null
+++ b/tests/Feature/Companies/Api/UpdateCompaniesTest.php
@@ -0,0 +1,53 @@
+create();
+
+ $this->actingAsForApi(User::factory()->create())
+ ->patchJson(route('api.companies.update', $company))
+ ->assertForbidden();
+ }
+
+ public function testValidationForPatchingCompany()
+ {
+ $company = Company::factory()->create();
+
+ $this->actingAsForApi(User::factory()->editCompanies()->create())
+ ->patchJson(route('api.companies.update', ['company' => $company->id]), [
+ 'name' => '',
+ ])
+ ->assertStatus(200)
+ ->assertStatusMessageIs('error')
+ ->assertJsonStructure([
+ 'messages' => [
+ 'name',
+ ],
+ ]);
+ }
+
+ public function testCanPatchCompany()
+ {
+ $company = Company::factory()->create();
+
+ $this->actingAsForApi(User::factory()->editCompanies()->create())
+ ->patchJson(route('api.companies.update', ['company' => $company->id]), [
+ 'name' => 'A Changed Name',
+ 'notes' => 'A Changed Note',
+ ])
+ ->assertStatus(200)
+ ->assertStatusMessageIs('success');
+
+ $company->refresh();
+ $this->assertEquals('A Changed Name', $company->name);
+ $this->assertEquals('A Changed Note', $company->notes);
+ }
+}
diff --git a/tests/Feature/Companies/Ui/EditCompanyTest.php b/tests/Feature/Companies/Ui/EditCompanyTest.php
index 7dcbc7908..febc71704 100644
--- a/tests/Feature/Companies/Ui/EditCompanyTest.php
+++ b/tests/Feature/Companies/Ui/EditCompanyTest.php
@@ -11,7 +11,7 @@ class EditCompanyTest extends TestCase
public function testPageRenders()
{
$this->actingAs(User::factory()->superuser()->create())
- ->get(route('companies.edit', Company::factory()->create()->id))
+ ->get(route('companies.edit', Company::factory()->create()))
->assertOk();
}
}
diff --git a/tests/Feature/Companies/Ui/ShowCompanyTest.php b/tests/Feature/Companies/Ui/ShowCompanyTest.php
index 1c41f6ab2..de7429d40 100644
--- a/tests/Feature/Companies/Ui/ShowCompanyTest.php
+++ b/tests/Feature/Companies/Ui/ShowCompanyTest.php
@@ -11,7 +11,7 @@ class ShowCompanyTest extends TestCase
public function testPageRenders()
{
$this->actingAs(User::factory()->superuser()->create())
- ->get(route('companies.show', Company::factory()->create()->id))
+ ->get(route('companies.show', Company::factory()->create()))
->assertOk();
}
}
diff --git a/tests/Feature/Components/Api/DeleteComponentsTest.php b/tests/Feature/Components/Api/DeleteComponentTest.php
similarity index 83%
rename from tests/Feature/Components/Api/DeleteComponentsTest.php
rename to tests/Feature/Components/Api/DeleteComponentTest.php
index e95fe3455..80d22d389 100644
--- a/tests/Feature/Components/Api/DeleteComponentsTest.php
+++ b/tests/Feature/Components/Api/DeleteComponentTest.php
@@ -9,7 +9,7 @@ use Tests\Concerns\TestsFullMultipleCompaniesSupport;
use Tests\Concerns\TestsPermissionsRequirement;
use Tests\TestCase;
-class DeleteComponentsTest extends TestCase implements TestsFullMultipleCompaniesSupport, TestsPermissionsRequirement
+class DeleteComponentTest extends TestCase implements TestsFullMultipleCompaniesSupport, TestsPermissionsRequirement
{
public function testRequiresPermission()
{
@@ -63,4 +63,13 @@ class DeleteComponentsTest extends TestCase implements TestsFullMultipleCompanie
$this->assertSoftDeleted($component);
}
+
+ public function testCannotDeleteComponentIfCheckedOut()
+ {
+ $component = Component::factory()->checkedOutToAsset()->create();
+
+ $this->actingAsForApi(User::factory()->deleteComponents()->create())
+ ->deleteJson(route('api.components.destroy', $component))
+ ->assertStatusMessageIs('error');
+ }
}
diff --git a/tests/Feature/Components/Ui/DeleteComponentTest.php b/tests/Feature/Components/Ui/DeleteComponentTest.php
index a0b813153..31d62f65d 100644
--- a/tests/Feature/Components/Ui/DeleteComponentTest.php
+++ b/tests/Feature/Components/Ui/DeleteComponentTest.php
@@ -40,6 +40,16 @@ class DeleteComponentTest extends TestCase implements TestsFullMultipleCompanies
$this->assertSoftDeleted($component);
}
+ public function testCannotDeleteComponentIfCheckedOut()
+ {
+ $component = Component::factory()->checkedOutToAsset()->create();
+
+ $this->actingAs(User::factory()->deleteComponents()->create())
+ ->delete(route('components.destroy', $component->id))
+ ->assertSessionHas('error')
+ ->assertRedirect(route('components.index'));
+ }
+
public function testDeletingComponentRemovesComponentImage()
{
Storage::fake('public');
diff --git a/tests/Feature/Components/Ui/EditComponentTest.php b/tests/Feature/Components/Ui/EditComponentTest.php
index 257034390..4e4b99233 100644
--- a/tests/Feature/Components/Ui/EditComponentTest.php
+++ b/tests/Feature/Components/Ui/EditComponentTest.php
@@ -11,7 +11,7 @@ class EditComponentTest extends TestCase
public function testPageRenders()
{
$this->actingAs(User::factory()->superuser()->create())
- ->get(route('components.edit', Component::factory()->create()->id))
+ ->get(route('components.edit', Component::factory()->create()))
->assertOk();
}
}
diff --git a/tests/Feature/Components/Ui/ShowComponentTest.php b/tests/Feature/Components/Ui/ShowComponentTest.php
index 50a17ee05..6ee2ca286 100644
--- a/tests/Feature/Components/Ui/ShowComponentTest.php
+++ b/tests/Feature/Components/Ui/ShowComponentTest.php
@@ -11,7 +11,7 @@ class ShowComponentTest extends TestCase
public function testPageRenders()
{
$this->actingAs(User::factory()->superuser()->create())
- ->get(route('components.show', Component::factory()->create()->id))
+ ->get(route('components.show', Component::factory()->create()))
->assertOk();
}
}
diff --git a/tests/Feature/Console/SendAcceptanceReminderTest.php b/tests/Feature/Console/SendAcceptanceReminderTest.php
index 9743a9db4..ee28e0935 100644
--- a/tests/Feature/Console/SendAcceptanceReminderTest.php
+++ b/tests/Feature/Console/SendAcceptanceReminderTest.php
@@ -43,9 +43,13 @@ class SendAcceptanceReminderTest extends TestCase
CheckoutAcceptance::factory()->pending()->create([
'assigned_to_id' => $userA->id,
]);
-
+ $headers = ['ID', 'Name'];
+ $rows = [
+ [$userA->id, $userA->present()->fullName()],
+ ];
$this->artisan('snipeit:acceptance-reminder')
- ->expectsOutput($userA->present()->fullName().' has no email address.')
+ ->expectsOutput("The following users do not have an email address:")
+ ->expectsTable($headers, $rows)
->assertExitCode(0);
Mail::assertNotSent(UnacceptedAssetReminderMail::class);
diff --git a/tests/Feature/Consumables/Ui/EditConsumableTest.php b/tests/Feature/Consumables/Ui/EditConsumableTest.php
index 6b75db127..308b7647b 100644
--- a/tests/Feature/Consumables/Ui/EditConsumableTest.php
+++ b/tests/Feature/Consumables/Ui/EditConsumableTest.php
@@ -11,7 +11,7 @@ class EditConsumableTest extends TestCase
public function testPageRenders()
{
$this->actingAs(User::factory()->superuser()->create())
- ->get(route('consumables.show', Consumable::factory()->create()->id))
+ ->get(route('consumables.show', Consumable::factory()->create()))
->assertOk();
}
}
diff --git a/tests/Feature/Consumables/Ui/UpdateConsumableTest.php b/tests/Feature/Consumables/Ui/UpdateConsumableTest.php
index 66fc9a11f..dbb13b9ce 100644
--- a/tests/Feature/Consumables/Ui/UpdateConsumableTest.php
+++ b/tests/Feature/Consumables/Ui/UpdateConsumableTest.php
@@ -29,7 +29,7 @@ class UpdateConsumableTest extends TestCase
$userForCompanyB = User::factory()->editConsumables()->for($companyB)->create();
$this->actingAs($userForCompanyB)
- ->get(route('consumables.edit', $consumableForCompanyA->id))
+ ->get(route('consumables.edit', $consumableForCompanyA))
->assertRedirect(route('consumables.index'));
}
@@ -51,10 +51,10 @@ class UpdateConsumableTest extends TestCase
$userForCompanyB = User::factory()->editConsumables()->for($companyB)->create();
$this->actingAs($userForCompanyB)
- ->put(route('consumables.update', $consumableForCompanyA->id), [
+ ->put(route('consumables.update', $consumableForCompanyA), [
//
])
- ->assertForbidden();
+ ->assertStatus(302);
}
public function testCannotSetQuantityToAmountLowerThanWhatIsCheckedOut()
@@ -99,7 +99,7 @@ class UpdateConsumableTest extends TestCase
];
$this->actingAs(User::factory()->createConsumables()->editConsumables()->create())
- ->put(route('consumables.update', $consumable->id), $data + [
+ ->put(route('consumables.update', $consumable), $data + [
'redirect_option' => 'index',
'category_type' => 'consumable',
])
diff --git a/tests/Feature/Departments/Api/CreateDepartmentsTest.php b/tests/Feature/Departments/Api/CreateDepartmentsTest.php
index a8725c5ff..e0f975dd7 100644
--- a/tests/Feature/Departments/Api/CreateDepartmentsTest.php
+++ b/tests/Feature/Departments/Api/CreateDepartmentsTest.php
@@ -20,4 +20,23 @@ class CreateDepartmentsTest extends TestCase
->assertForbidden();
}
+ public function testCanCreateDepartment()
+ {
+ $response = $this->actingAsForApi(User::factory()->superuser()->create())
+ ->postJson(route('api.departments.store'), [
+ 'name' => 'Test Department',
+ 'notes' => 'Test Note',
+ ])
+ ->assertOk()
+ ->assertStatusMessageIs('success')
+ ->assertStatus(200)
+ ->json();
+
+ $this->assertTrue(Department::where('name', 'Test Department')->exists());
+
+ $department = Department::find($response['payload']['id']);
+ $this->assertEquals('Test Department', $department->name);
+ $this->assertEquals('Test Note', $department->notes);
+ }
+
}
diff --git a/tests/Feature/Departments/Api/UpdateDepartmentsTest.php b/tests/Feature/Departments/Api/UpdateDepartmentsTest.php
index 2a6401e7f..b4eb38301 100644
--- a/tests/Feature/Departments/Api/UpdateDepartmentsTest.php
+++ b/tests/Feature/Departments/Api/UpdateDepartmentsTest.php
@@ -25,6 +25,7 @@ class UpdateDepartmentsTest extends TestCase
$this->actingAsForApi(User::factory()->superuser()->create())
->patchJson(route('api.departments.update', $department), [
'name' => 'Test Department',
+ 'notes' => 'Test Note',
])
->assertOk()
->assertStatusMessageIs('success')
@@ -33,6 +34,7 @@ class UpdateDepartmentsTest extends TestCase
$department->refresh();
$this->assertEquals('Test Department', $department->name, 'Name was not updated');
+ $this->assertEquals('Test Note', $department->notes, 'Note was not updated');
}
diff --git a/tests/Feature/Departments/Ui/CreateDepartmentsTest.php b/tests/Feature/Departments/Ui/CreateDepartmentsTest.php
index 08dc12ba0..f20a2c7b6 100644
--- a/tests/Feature/Departments/Ui/CreateDepartmentsTest.php
+++ b/tests/Feature/Departments/Ui/CreateDepartmentsTest.php
@@ -34,11 +34,12 @@ class CreateDepartmentsTest extends TestCase
$this->actingAs(User::factory()->superuser()->create())
->post(route('departments.store'), [
'name' => 'Test Department',
- 'company_id' => Company::factory()->create()->id
+ 'company_id' => Company::factory()->create()->id,
+ 'notes' => 'Test Note',
])
->assertRedirect(route('departments.index'));
- $this->assertTrue(Department::where('name', 'Test Department')->exists());
+ $this->assertTrue(Department::where('name', 'Test Department')->where('notes', 'Test Note')->exists());
}
diff --git a/tests/Feature/Departments/Ui/ShowDepartmentTest.php b/tests/Feature/Departments/Ui/ShowDepartmentTest.php
index d8a007bde..f1373ccc2 100644
--- a/tests/Feature/Departments/Ui/ShowDepartmentTest.php
+++ b/tests/Feature/Departments/Ui/ShowDepartmentTest.php
@@ -11,7 +11,7 @@ class ShowDepartmentTest extends TestCase
public function testPageRenders()
{
$this->actingAs(User::factory()->superuser()->create())
- ->get(route('departments.show', Department::factory()->create()->id))
+ ->get(route('departments.show', Department::factory()->create()))
->assertOk();
}
}
diff --git a/tests/Feature/Departments/Ui/UpdateDepartmentsTest.php b/tests/Feature/Departments/Ui/UpdateDepartmentsTest.php
index 71f7cfe47..83e52bf7c 100644
--- a/tests/Feature/Departments/Ui/UpdateDepartmentsTest.php
+++ b/tests/Feature/Departments/Ui/UpdateDepartmentsTest.php
@@ -22,7 +22,7 @@ class UpdateDepartmentsTest extends TestCase
public function testPageRenders()
{
$this->actingAs(User::factory()->superuser()->create())
- ->get(route('departments.edit', Department::factory()->create()->id))
+ ->get(route('departments.edit', Department::factory()->create()))
->assertOk();
}
@@ -32,15 +32,16 @@ class UpdateDepartmentsTest extends TestCase
$this->assertTrue(Department::where('name', 'Test Department')->exists());
$response = $this->actingAs(User::factory()->superuser()->create())
- ->put(route('departments.update', ['department' => $department]), [
+ ->put(route('departments.update', $department), [
'name' => 'Test Department Edited',
+ 'notes' => 'Test Note Edited',
])
->assertStatus(302)
->assertSessionHasNoErrors()
->assertRedirect(route('departments.index'));
$this->followRedirects($response)->assertSee('Success');
- $this->assertTrue(Department::where('name', 'Test Department Edited')->exists());
+ $this->assertTrue(Department::where('name', 'Test Department Edited')->where('notes', 'Test Note Edited')->exists());
}
diff --git a/tests/Feature/Groups/Api/StoreGroupTest.php b/tests/Feature/Groups/Api/StoreGroupTest.php
index ebcbff71c..484c921a6 100644
--- a/tests/Feature/Groups/Api/StoreGroupTest.php
+++ b/tests/Feature/Groups/Api/StoreGroupTest.php
@@ -21,6 +21,7 @@ class StoreGroupTest extends TestCase
$this->actingAsForApi(User::factory()->superuser()->create())
->postJson(route('api.groups.store'), [
'name' => 'My Awesome Group',
+ 'notes' => 'My Awesome Note',
'permissions' => [
'admin' => '1',
'import' => '1',
@@ -29,7 +30,7 @@ class StoreGroupTest extends TestCase
])
->assertOk();
- $group = Group::where('name', 'My Awesome Group')->first();
+ $group = Group::where('name', 'My Awesome Group')->where('notes', 'My Awesome Note')->first();
$this->assertNotNull($group);
$this->assertEquals('1', $group->decodePermissions()['admin']);
diff --git a/tests/Feature/Groups/Ui/CreateGroupTest.php b/tests/Feature/Groups/Ui/CreateGroupTest.php
index 56b3a7605..796905c50 100644
--- a/tests/Feature/Groups/Ui/CreateGroupTest.php
+++ b/tests/Feature/Groups/Ui/CreateGroupTest.php
@@ -2,6 +2,7 @@
namespace Tests\Feature\Groups\Ui;
+use App\Models\Group;
use App\Models\User;
use Tests\TestCase;
@@ -13,4 +14,18 @@ class CreateGroupTest extends TestCase
->get(route('groups.create'))
->assertOk();
}
+
+ public function testUserCanCreateGroup()
+ {
+ $this->assertFalse(Group::where('name', 'Test Group')->exists());
+
+ $this->actingAs(User::factory()->superuser()->create())
+ ->post(route('groups.store'), [
+ 'name' => 'Test Group',
+ 'notes' => 'Test Note',
+ ])
+ ->assertRedirect(route('groups.index'));
+
+ $this->assertTrue(Group::where('name', 'Test Group')->where('notes', 'Test Note')->exists());
+ }
}
diff --git a/tests/Feature/Groups/Ui/UpdateGroupTest.php b/tests/Feature/Groups/Ui/UpdateGroupTest.php
index c68d7cb84..edb236872 100644
--- a/tests/Feature/Groups/Ui/UpdateGroupTest.php
+++ b/tests/Feature/Groups/Ui/UpdateGroupTest.php
@@ -14,4 +14,22 @@ class UpdateGroupTest extends TestCase
->get(route('groups.edit', Group::factory()->create()->id))
->assertOk();
}
+
+ public function testUserCanEditGroups()
+ {
+ $group = Group::factory()->create(['name' => 'Test Group']);
+ $this->assertTrue(Group::where('name', 'Test Group')->exists());
+
+ $response = $this->actingAs(User::factory()->superuser()->create())
+ ->put(route('groups.update', ['group' => $group]), [
+ 'name' => 'Test Group Edited',
+ 'notes' => 'Test Note Edited',
+ ])
+ ->assertStatus(302)
+ ->assertSessionHasNoErrors()
+ ->assertRedirect(route('groups.index'));
+
+ $this->followRedirects($response)->assertSee('Success');
+ $this->assertTrue(Group::where('name', 'Test Group Edited')->where('notes', 'Test Note Edited')->exists());
+ }
}
diff --git a/tests/Feature/Importing/Api/ImportConsumablesTest.php b/tests/Feature/Importing/Api/ImportConsumablesTest.php
index 81bfdf4bf..cbc8627f5 100644
--- a/tests/Feature/Importing/Api/ImportConsumablesTest.php
+++ b/tests/Feature/Importing/Api/ImportConsumablesTest.php
@@ -83,7 +83,7 @@ class ImportConsumablesTest extends ImportDataTestCase implements TestsPermissio
$this->assertEquals($row['category'], $newConsumable->category->name);
$this->assertEquals($row['location'], $newConsumable->location->name);
$this->assertEquals($row['companyName'], $newConsumable->company->name);
- $this->assertNull($newConsumable->supplier_id);
+ $this->assertNotNull($newConsumable->supplier_id);
$this->assertFalse($newConsumable->requestable);
$this->assertNull($newConsumable->image);
$this->assertEquals($row['orderNumber'], $newConsumable->order_number);
@@ -233,7 +233,7 @@ class ImportConsumablesTest extends ImportDataTestCase implements TestsPermissio
$this->assertEquals($row['purchaseDate'], $updatedConsumable->purchase_date->toDateString());
$this->assertEquals($row['purchaseCost'], $updatedConsumable->purchase_cost);
- $this->assertEquals($consumable->supplier_id, $updatedConsumable->supplier_id);
+ $this->assertEquals($row['supplier'], $updatedConsumable->supplier->name);
$this->assertEquals($consumable->requestable, $updatedConsumable->requestable);
$this->assertEquals($consumable->min_amt, $updatedConsumable->min_amt);
$this->assertEquals($consumable->model_number, $updatedConsumable->model_number);
@@ -265,6 +265,8 @@ class ImportConsumablesTest extends ImportDataTestCase implements TestsPermissio
$this->actingAsForApi(User::factory()->superuser()->create());
+
+ // This mapping is incorrect on purpose
$this->importFileResponse([
'import' => $import->id,
'column-mappings' => [
@@ -290,7 +292,7 @@ class ImportConsumablesTest extends ImportDataTestCase implements TestsPermissio
$this->assertEquals($row['purchaseDate'], $newConsumable->company->name);
$this->assertEquals($row['companyName'], $newConsumable->qty);
$this->assertEquals($row['quantity'], $newConsumable->name);
- $this->assertNull($newConsumable->supplier_id);
+ $this->assertNotNull($newConsumable->supplier_id);
$this->assertFalse($newConsumable->requestable);
$this->assertNull($newConsumable->image);
$this->assertEquals($row['orderNumber'], $newConsumable->order_number);
diff --git a/tests/Feature/Importing/Ui/ImportTest.php b/tests/Feature/Importing/Ui/ImportTest.php
index 3493f47af..b549dc415 100644
--- a/tests/Feature/Importing/Ui/ImportTest.php
+++ b/tests/Feature/Importing/Ui/ImportTest.php
@@ -3,6 +3,8 @@
namespace Tests\Feature\Importing\Ui;
use App\Models\User;
+use Illuminate\Http\UploadedFile;
+use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class ImportTest extends TestCase
@@ -13,4 +15,54 @@ class ImportTest extends TestCase
->get(route('imports.index'))
->assertOk();
}
+
+ public function testStoreInternationalAsset(): void
+ {
+ $evil_string = 'це'; //cyrillic - windows-1251 (ONE)
+ $csv_contents = "a,b,c\n$evil_string,$evil_string,$evil_string\n";
+
+ // now, deliberately transform our UTF-8 into Windows-1251 so we can test out the character-set detection
+ $transliterated_contents = iconv('UTF-8', 'WINDOWS-1251', $csv_contents);
+ //\Log::error("RAW TRANSLITERATED CONTENTS: $transliterated_contents"); // should show 'unicode missing glyph' symbol in logs.
+
+ $this->actingAsForApi(User::factory()->superuser()->create());
+ $results = $this->post(route('api.imports.store'), ['files' => [UploadedFile::fake()->createWithContent("myname.csv", $transliterated_contents)]])
+ ->assertOk()
+ ->assertJsonStructure([
+ "files" => [
+ [
+ "created_at",
+ "field_map",
+ "file_path",
+ "filesize",
+ "first_row",
+ "header_row",
+ "id",
+ "import_type",
+ "name",
+ ]
+ ]
+ ]);
+ $this->assertEquals($evil_string, $results->json()['files'][0]['first_row'][0]);
+ }
+
+ public function testStoreInternationalAssetMisparse(): void
+ {
+ $evil_maker = function ($arr) {
+ $results = '';
+ foreach ($arr as $thing) {
+ $results .= chr($thing);
+ }
+ return $results;
+ };
+
+ // 0xC0 makes it 'not unicode', and 0xFF makes it 'likely WINDOWS-1251', and 0x98 at the end makes it 'not-valid-Windows-1251'
+ $evil_content = $evil_maker([0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x98]);
+
+ $this->actingAsForApi(User::factory()->superuser()->create());
+ $results = $this->post(route('api.imports.store'), ['files' => [UploadedFile::fake()->createWithContent("myname.csv", $evil_content)]])
+ ->assertStatus(422)
+ ->assertStatusMessageIs('error')
+ ->assertMessagesAre(trans('admin/hardware/message.import.transliterate_failure', ["encoding" => "windows-1251"]));
+ }
}
diff --git a/tests/Feature/Licenses/Ui/UpdateLicenseTest.php b/tests/Feature/Licenses/Ui/UpdateLicenseTest.php
index c3d1446d5..7b1b5b29e 100644
--- a/tests/Feature/Licenses/Ui/UpdateLicenseTest.php
+++ b/tests/Feature/Licenses/Ui/UpdateLicenseTest.php
@@ -11,7 +11,7 @@ class UpdateLicenseTest extends TestCase
public function testPageRenders()
{
$this->actingAs(User::factory()->superuser()->create())
- ->get(route('licenses.update', License::factory()->create()->id))
+ ->get(route('licenses.edit', License::factory()->create()->id))
->assertOk();
}
}
diff --git a/tests/Feature/Livewire/CategoryEditFormTest.php b/tests/Feature/Livewire/CategoryEditFormTest.php
index a439f544a..4596af120 100644
--- a/tests/Feature/Livewire/CategoryEditFormTest.php
+++ b/tests/Feature/Livewire/CategoryEditFormTest.php
@@ -10,7 +10,10 @@ class CategoryEditFormTest extends TestCase
{
public function testTheComponentCanRender()
{
- Livewire::test(CategoryEditForm::class)->assertStatus(200);
+ Livewire::test(CategoryEditForm::class, [
+ 'sendCheckInEmail' => true,
+ 'useDefaultEula' => true,
+ ])->assertStatus(200);
}
public function testSendEmailCheckboxIsCheckedOnLoadWhenSendEmailIsExistingSetting()
diff --git a/tests/Feature/Locations/Api/CreateLocationsTest.php b/tests/Feature/Locations/Api/CreateLocationsTest.php
index 171508b72..0a75517f3 100644
--- a/tests/Feature/Locations/Api/CreateLocationsTest.php
+++ b/tests/Feature/Locations/Api/CreateLocationsTest.php
@@ -16,6 +16,26 @@ class CreateLocationsTest extends TestCase
->assertForbidden();
}
+
+ public function testCanCreateLocation()
+ {
+ $response = $this->actingAsForApi(User::factory()->superuser()->create())
+ ->postJson(route('api.locations.store'), [
+ 'name' => 'Test Location',
+ 'notes' => 'Test Note',
+ ])
+ ->assertOk()
+ ->assertStatusMessageIs('success')
+ ->assertStatus(200)
+ ->json();
+
+ $this->assertTrue(Location::where('name', 'Test Location')->exists());
+
+ $department = Location::find($response['payload']['id']);
+ $this->assertEquals('Test Location', $department->name);
+ $this->assertEquals('Test Note', $department->notes);
+ }
+
public function testCannotCreateNewLocationsWithTheSameName()
{
$location = Location::factory()->create();
diff --git a/tests/Feature/Locations/Api/UpdateLocationsTest.php b/tests/Feature/Locations/Api/UpdateLocationsTest.php
index a3dd8c228..a169ba87b 100644
--- a/tests/Feature/Locations/Api/UpdateLocationsTest.php
+++ b/tests/Feature/Locations/Api/UpdateLocationsTest.php
@@ -22,7 +22,8 @@ class UpdateLocationsTest extends TestCase
$this->actingAsForApi(User::factory()->superuser()->create())
->patchJson(route('api.locations.update', $location), [
- 'name' => 'Test Location',
+ 'name' => 'Test Updated Location',
+ 'notes' => 'Test Updated Note',
])
->assertOk()
->assertStatusMessageIs('success')
@@ -30,7 +31,8 @@ class UpdateLocationsTest extends TestCase
->json();
$location->refresh();
- $this->assertEquals('Test Location', $location->name, 'Name was not updated');
+ $this->assertEquals('Test Updated Location', $location->name, 'Name was not updated');
+ $this->assertEquals('Test Updated Note', $location->notes, 'Note was not updated');
}
diff --git a/tests/Feature/Locations/Ui/CreateLocationsTest.php b/tests/Feature/Locations/Ui/CreateLocationsTest.php
index 2e2e1e0aa..794ee06c6 100644
--- a/tests/Feature/Locations/Ui/CreateLocationsTest.php
+++ b/tests/Feature/Locations/Ui/CreateLocationsTest.php
@@ -33,11 +33,11 @@ class CreateLocationsTest extends TestCase
$this->actingAs(User::factory()->superuser()->create())
->post(route('locations.store'), [
'name' => 'Test Location',
- 'company_id' => Company::factory()->create()->id
+ 'notes' => 'Test Note',
])
->assertRedirect(route('locations.index'));
- $this->assertTrue(Location::where('name', 'Test Location')->exists());
+ $this->assertTrue(Location::where('name', 'Test Location')->where('notes', 'Test Note')->exists());
}
public function testUserCannotCreateLocationsWithInvalidParent()
diff --git a/tests/Feature/Locations/Ui/ShowLocationTest.php b/tests/Feature/Locations/Ui/ShowLocationTest.php
index 6afb7dfe5..394ed73f4 100644
--- a/tests/Feature/Locations/Ui/ShowLocationTest.php
+++ b/tests/Feature/Locations/Ui/ShowLocationTest.php
@@ -11,7 +11,7 @@ class ShowLocationTest extends TestCase
public function testPageRenders()
{
$this->actingAs(User::factory()->superuser()->create())
- ->get(route('locations.show', Location::factory()->create()->id))
+ ->get(route('locations.show', Location::factory()->create()))
->assertOk();
}
}
diff --git a/tests/Feature/Locations/Ui/UpdateLocationsTest.php b/tests/Feature/Locations/Ui/UpdateLocationsTest.php
index 6cead815b..22d9ce577 100644
--- a/tests/Feature/Locations/Ui/UpdateLocationsTest.php
+++ b/tests/Feature/Locations/Ui/UpdateLocationsTest.php
@@ -21,7 +21,7 @@ class UpdateLocationsTest extends TestCase
public function testPageRenders()
{
$this->actingAs(User::factory()->superuser()->create())
- ->get(route('locations.update', Location::factory()->create()->id))
+ ->get(route('locations.edit', Location::factory()->create()))
->assertOk();
}
@@ -33,13 +33,14 @@ class UpdateLocationsTest extends TestCase
$response = $this->actingAs(User::factory()->superuser()->create())
->put(route('locations.update', ['location' => $location]), [
'name' => 'Test Location Edited',
+ 'notes' => 'Test Note Edited',
])
->assertStatus(302)
->assertSessionHasNoErrors()
->assertRedirect(route('locations.index'));
$this->followRedirects($response)->assertSee('Success');
- $this->assertTrue(Location::where('name', 'Test Location Edited')->exists());
+ $this->assertTrue(Location::where('name', 'Test Location Edited')->where('notes', 'Test Note Edited')->exists());
}
public function testUserCannotEditLocationsToMakeThemTheirOwnParent()
@@ -47,8 +48,8 @@ class UpdateLocationsTest extends TestCase
$location = Location::factory()->create();
$response = $this->actingAs(User::factory()->superuser()->create())
- ->from(route('locations.edit', ['location' => $location->id]))
- ->put(route('locations.update', ['location' => $location]), [
+ ->from(route('locations.edit', $location))
+ ->put(route('locations.update', $location), [
'name' => 'Test Location',
'parent_id' => $location->id,
])
@@ -62,7 +63,7 @@ class UpdateLocationsTest extends TestCase
{
$location = Location::factory()->create();
$response = $this->actingAs(User::factory()->superuser()->create())
- ->from(route('locations.edit', ['location' => $location->id]))
+ ->from(route('locations.edit', $location))
->put(route('locations.update', ['location' => $location]), [
'name' => 'Test Location',
'parent_id' => '100000000'
diff --git a/tests/Feature/Manufacturers/Api/CreateManufacturersTest.php b/tests/Feature/Manufacturers/Api/CreateManufacturersTest.php
new file mode 100644
index 000000000..9dc953148
--- /dev/null
+++ b/tests/Feature/Manufacturers/Api/CreateManufacturersTest.php
@@ -0,0 +1,39 @@
+actingAsForApi(User::factory()->create())
+ ->postJson(route('api.departments.store'))
+ ->assertForbidden();
+ }
+
+ public function testCanCreateManufacturer()
+ {
+ $response = $this->actingAsForApi(User::factory()->superuser()->create())
+ ->postJson(route('api.manufacturers.store'), [
+ 'name' => 'Test Manufacturer',
+ 'notes' => 'Test Note',
+ ])
+ ->assertOk()
+ ->assertStatusMessageIs('success')
+ ->assertStatus(200)
+ ->json();
+
+ $this->assertTrue(Manufacturer::where('name', 'Test Manufacturer')->where('notes', 'Test Note')->exists());
+
+ $manufacturer = Manufacturer::find($response['payload']['id']);
+ $this->assertEquals('Test Manufacturer', $manufacturer->name);
+ $this->assertEquals('Test Note', $manufacturer->notes);
+ }
+
+}
diff --git a/tests/Feature/Manufacturers/Api/UpdateManufacturersTest.php b/tests/Feature/Manufacturers/Api/UpdateManufacturersTest.php
new file mode 100644
index 000000000..f40052f2b
--- /dev/null
+++ b/tests/Feature/Manufacturers/Api/UpdateManufacturersTest.php
@@ -0,0 +1,50 @@
+actingAs(User::factory()->create())
+ ->post(route('manufacturers.store'), [
+ 'name' => 'Test Manufacturer',
+ ])
+ ->assertStatus(403)
+ ->assertForbidden();
+ }
+
+ public function testPageRenders()
+ {
+ $this->actingAs(User::factory()->superuser()->create())
+ ->get(route('manufacturers.edit', Manufacturer::factory()->create()->id))
+ ->assertOk();
+ }
+
+ public function testUserCanEditManufacturers()
+ {
+ $department = Manufacturer::factory()->create(['name' => 'Test Manufacturer']);
+ $this->assertTrue(Manufacturer::where('name', 'Test Manufacturer')->exists());
+
+ $response = $this->actingAs(User::factory()->superuser()->create())
+ ->put(route('manufacturers.update', ['manufacturer' => $department]), [
+ 'name' => 'Test Manufacturer Edited',
+ 'notes' => 'Test Note Edited',
+ ])
+ ->assertStatus(302)
+ ->assertSessionHasNoErrors()
+ ->assertRedirect(route('manufacturers.index'));
+
+ $this->followRedirects($response)->assertSee('Success');
+ $this->assertTrue(Manufacturer::where('name', 'Test Manufacturer Edited')->where('notes', 'Test Note Edited')->exists());
+
+ }
+
+
+
+}
diff --git a/tests/Feature/Manufacturers/Ui/CreateManufacturerTest.php b/tests/Feature/Manufacturers/Ui/CreateManufacturerTest.php
index 133b271ea..4eb9d4fb7 100644
--- a/tests/Feature/Manufacturers/Ui/CreateManufacturerTest.php
+++ b/tests/Feature/Manufacturers/Ui/CreateManufacturerTest.php
@@ -3,6 +3,7 @@
namespace Tests\Feature\Manufacturers\Ui;
use App\Models\User;
+use App\Models\Manufacturer;
use Tests\TestCase;
class CreateManufacturerTest extends TestCase
@@ -13,4 +14,19 @@ class CreateManufacturerTest extends TestCase
->get(route('manufacturers.create'))
->assertOk();
}
+
+ public function testUserCanCreateManufacturer()
+ {
+ $this->assertFalse(Manufacturer::where('name', 'Test Manufacturer')->exists());
+
+ $this->actingAs(User::factory()->superuser()->create())
+ ->post(route('manufacturers.store'), [
+ 'name' => 'Test Manufacturer',
+ 'notes' => 'Test Note',
+ ])
+ ->assertRedirect(route('manufacturers.index'));
+
+ $this->assertTrue(Manufacturer::where('name', 'Test Manufacturer')->where('notes', 'Test Note')->exists());
+ }
+
}
diff --git a/tests/Feature/Manufacturers/Ui/ShowManufacturerTest.php b/tests/Feature/Manufacturers/Ui/ShowManufacturerTest.php
index 890feb123..9a40819a1 100644
--- a/tests/Feature/Manufacturers/Ui/ShowManufacturerTest.php
+++ b/tests/Feature/Manufacturers/Ui/ShowManufacturerTest.php
@@ -11,7 +11,7 @@ class ShowManufacturerTest extends TestCase
public function testPageRenders()
{
$this->actingAs(User::factory()->superuser()->create())
- ->get(route('manufacturers.show', Manufacturer::factory()->create()->id))
+ ->get(route('manufacturers.show', Manufacturer::factory()->create()))
->assertOk();
}
}
diff --git a/tests/Feature/Manufacturers/Ui/UpdateManufacturerTest.php b/tests/Feature/Manufacturers/Ui/UpdateManufacturerTest.php
index e2f7724fa..9d5861300 100644
--- a/tests/Feature/Manufacturers/Ui/UpdateManufacturerTest.php
+++ b/tests/Feature/Manufacturers/Ui/UpdateManufacturerTest.php
@@ -11,7 +11,26 @@ class UpdateManufacturerTest extends TestCase
public function testPageRenders()
{
$this->actingAs(User::factory()->superuser()->create())
- ->get(route('manufacturers.edit', Manufacturer::factory()->create()->id))
+ ->get(route('manufacturers.edit', Manufacturer::factory()->create()))
->assertOk();
}
+
+ public function testUserCanEditManufacturers()
+ {
+ $manufacturer = Manufacturer::factory()->create(['name' => 'Test Manufacturer']);
+ $this->assertTrue(Manufacturer::where('name', 'Test Manufacturer')->exists());
+
+ $response = $this->actingAs(User::factory()->superuser()->create())
+ ->put(route('manufacturers.update', $manufacturer), [
+ 'name' => 'Test Manufacturer Edited',
+ 'notes' => 'Test Note Edited',
+ ])
+ ->assertStatus(302)
+ ->assertSessionHasNoErrors()
+ ->assertRedirect(route('manufacturers.index'));
+
+ $this->followRedirects($response)->assertSee('Success');
+ $this->assertTrue(Manufacturer::where('name', 'Test Manufacturer Edited')->where('notes', 'Test Note Edited')->exists());
+ }
+
}
diff --git a/tests/Feature/Modals/Ui/ShowModalsTest.php b/tests/Feature/Modals/Ui/ShowModalsTest.php
new file mode 100644
index 000000000..6da781d31
--- /dev/null
+++ b/tests/Feature/Modals/Ui/ShowModalsTest.php
@@ -0,0 +1,66 @@
+createUsers()->create();
+ $response = $this->actingAs($admin)
+ ->get('modals/user')
+ ->assertOk();
+
+ $response->assertStatus(200);
+ $response->assertDontSee($admin->first_name);
+ $response->assertDontSee($admin->last_name);
+ $response->assertDontSee($admin->email);
+ }
+
+ public function testDepartmentModalRenders()
+ {
+ $this->actingAs(User::factory()->superuser()->create())
+ ->get('modals/model')
+ ->assertOk();
+ }
+
+ public function testStatusLabelModalRenders()
+ {
+ $this->actingAs(User::factory()->superuser()->create())
+ ->get('modals/statuslabel')
+ ->assertOk();
+ }
+
+ public function testLocationModalRenders()
+ {
+ $this->actingAs(User::factory()->superuser()->create())
+ ->get('modals/location')
+ ->assertOk();
+ }
+
+ public function testCategoryModalRenders()
+ {
+ $this->actingAs(User::factory()->superuser()->create())
+ ->get('modals/category')
+ ->assertOk();
+ }
+
+ public function testManufacturerModalRenders()
+ {
+ $this->actingAs(User::factory()->superuser()->create())
+ ->get('modals/manufacturer')
+ ->assertOk();
+ }
+
+ public function testSupplierModalRenders()
+ {
+ $this->actingAs(User::factory()->superuser()->create())
+ ->get('modals/supplier')
+ ->assertOk();
+ }
+
+
+}
diff --git a/tests/Feature/Notes/AssetNotesTest.php b/tests/Feature/Notes/AssetNotesTest.php
deleted file mode 100644
index c666cb9e3..000000000
--- a/tests/Feature/Notes/AssetNotesTest.php
+++ /dev/null
@@ -1,76 +0,0 @@
-create();
-
- $this->actingAsForApi(User::factory()->create())
- ->postJson(route('api.notes.store'), [
- 'note' => 'New Note!',
- 'type' => 'asset',
- 'id' => $asset->id,
- ])
- ->assertForbidden();
- }
-
- public function testValidation()
- {
- $asset = Asset::factory()->create();
-
- $this->actingAsForApi(User::factory()->editAssets()->create())
- ->postJson(route('api.notes.store'), [
- // 'note' => '',
- 'type' => 'a_type_not_asset',
- 'id' => $asset->id,
- ])
- ->assertOk()
- ->assertStatusMessageIs('error')
- ->assertJsonValidationErrors(['note', 'type'], 'messages');
- }
-
- public function testRequiresExistingAsset()
- {
- $this->actingAsForApi(User::factory()->editAssets()->create())
- ->postJson(route('api.notes.store'), [
- 'note' => 'New Note!',
- 'type' => 'asset',
- 'id' => 999_999,
- ])
- ->assertStatusMessageIs('error')
- ->assertMessagesAre('Asset not found');
- }
-
- public function testCanAddNoteToAsset()
- {
- Event::fake([NoteAdded::class]);
-
- $asset = Asset::factory()->create();
- $user = User::factory()->editAssets()->create();
-
- $this->actingAsForApi($user)
- ->postJson(route('api.notes.store'), [
- 'note' => 'New Note!',
- 'type' => 'asset',
- 'id' => $asset->id,
- ])
- ->assertOk()
- ->assertStatusMessageIs('success');
-
- Event::assertDispatchedTimes(NoteAdded::class, 1);
- Event::assertDispatched(NoteAdded::class, function (NoteAdded $event) use ($asset, $user) {
- return $event->itemNoteAddedOn->is($asset)
- && $event->note === 'New Note!'
- && $event->noteAddedBy->is($user);
- });
- }
-}
diff --git a/tests/Feature/Notes/CreateNotesTest.php b/tests/Feature/Notes/CreateNotesTest.php
new file mode 100644
index 000000000..a2caae645
--- /dev/null
+++ b/tests/Feature/Notes/CreateNotesTest.php
@@ -0,0 +1,70 @@
+actingAs(User::factory()->create())
+ ->post(route('notes.store'))
+ ->assertForbidden();
+ }
+
+ public function testValidation()
+ {
+ $asset = Asset::factory()->create();
+
+ $this->actingAs(User::factory()->editAssets()->create())
+ ->post(route('notes.store'), [
+ 'id' => $asset->id,
+ // should be more...
+ ])
+ ->assertSessionHas('errors');
+ }
+
+ public function testAssetMustExist()
+ {
+ $this->actingAs(User::factory()->editAssets()->create())
+ ->post(route('notes.store'), [
+ 'id' => 999_999,
+ 'type' => 'asset',
+ 'note' => 'my note',
+ ])
+ ->assertStatus(302);
+ }
+
+ public function testCanCreateNoteForAsset()
+ {
+ $actor = User::factory()->editAssets()->create();
+
+ $asset = Asset::factory()->create();
+
+ $this->actingAs($actor)
+ ->withHeader('User-Agent', 'Custom User Agent For Test')
+ ->post(route('notes.store'), [
+ '_token' => '_token-to-simulate-request-from-gui',
+ 'id' => $asset->id,
+ 'type' => 'asset',
+ 'note' => 'my special note',
+ ])
+ ->assertRedirect(route('hardware.show', $asset->id) . '#history')
+ ->assertSessionHas('success', trans('general.note_added'));
+
+ $this->assertDatabaseHas('action_logs', [
+ 'created_by' => $actor->id,
+ 'action_type' => 'note added',
+ 'target_id' => null,
+ 'target_type' => null,
+ 'note' => 'my special note',
+ 'item_type' => Asset::class,
+ 'item_id' => $asset->id,
+ 'action_source' => 'gui',
+ 'user_agent' => 'Custom User Agent For Test',
+ ]);
+ }
+}
diff --git a/tests/Feature/Notifications/Email/AssetAcceptanceReminderTest.php b/tests/Feature/Notifications/Email/AssetAcceptanceReminderTest.php
new file mode 100644
index 000000000..deb3e07d2
--- /dev/null
+++ b/tests/Feature/Notifications/Email/AssetAcceptanceReminderTest.php
@@ -0,0 +1,108 @@
+pending()->create();
+ $userWithoutPermission = User::factory()->create();
+
+ $this->actingAs($userWithoutPermission)
+ ->post($this->routeFor($checkoutAcceptance))
+ ->assertForbidden();
+
+ Mail::assertNotSent(CheckoutAssetMail::class);
+ }
+
+ public function testReminderNotSentIfAcceptanceDoesNotExist()
+ {
+ $this->actingAs(User::factory()->canViewReports()->create())
+ ->post(route('reports/unaccepted_assets_sent_reminder', [
+ 'acceptance_id' => 999999,
+ ]));
+
+ Mail::assertNotSent(CheckoutAssetMail::class);
+ }
+
+ public function testReminderNotSentIfAcceptanceAlreadyAccepted()
+ {
+ $checkoutAcceptanceAlreadyAccepted = CheckoutAcceptance::factory()->accepted()->create();
+
+ $this->actingAs(User::factory()->canViewReports()->create())
+ ->post($this->routeFor($checkoutAcceptanceAlreadyAccepted));
+
+ Mail::assertNotSent(CheckoutAssetMail::class);
+ }
+
+ public static function CheckoutAcceptancesToUsersWithoutEmailAddresses()
+ {
+ yield 'User with null email address' => [
+ function () {
+ return CheckoutAcceptance::factory()
+ ->pending()
+ ->forAssignedTo(['email' => null])
+ ->create();
+ }
+ ];
+
+ yield 'User with empty string email address' => [
+ function () {
+ return CheckoutAcceptance::factory()
+ ->pending()
+ ->forAssignedTo(['email' => ''])
+ ->create();
+ }
+ ];
+ }
+
+ #[DataProvider('CheckoutAcceptancesToUsersWithoutEmailAddresses')]
+ public function testUserWithoutEmailAddressHandledGracefully($callback)
+ {
+ $checkoutAcceptance = $callback();
+
+ $this->actingAs(User::factory()->canViewReports()->create())
+ ->post($this->routeFor($checkoutAcceptance))
+ // check we didn't crash...
+ ->assertRedirect();
+
+ Mail::assertNotSent(CheckoutAssetMail::class);
+ }
+
+ public function testReminderIsSentToUser()
+ {
+ $checkoutAcceptance = CheckoutAcceptance::factory()->pending()->create();
+
+ $this->actingAs(User::factory()->canViewReports()->create())
+ ->post($this->routeFor($checkoutAcceptance))
+ ->assertRedirect(route('reports/unaccepted_assets'));
+
+ Mail::assertSent(CheckoutAssetMail::class, 1);
+ Mail::assertSent(CheckoutAssetMail::class, function (CheckoutAssetMail $mail) use ($checkoutAcceptance) {
+ return $mail->hasTo($checkoutAcceptance->assignedTo->email)
+ && $mail->hasSubject(trans('mail.unaccepted_asset_reminder'));
+ });
+ }
+
+ private function routeFor(CheckoutAcceptance $checkoutAcceptance): string
+ {
+ return route('reports/unaccepted_assets_sent_reminder', [
+ 'acceptance_id' => $checkoutAcceptance->id,
+ ]);
+ }
+}
diff --git a/tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php b/tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php
new file mode 100644
index 000000000..1d91cf255
--- /dev/null
+++ b/tests/Feature/Notifications/Email/ExpiringAlertsNotificationTest.php
@@ -0,0 +1,127 @@
+markIncompleteIfSqlite();
+ Mail::fake();
+
+ $this->settings->enableAlertEmail('admin@example.com');
+ $this->settings->setAlertInterval(30);
+
+ $alert_email = Setting::first()->alert_email;
+
+ $expiringAsset = Asset::factory()->create([
+ 'purchase_date' => now()->subDays(350)->format('Y-m-d'),
+ 'warranty_months' => 12,
+ 'archived' => 0,
+ 'deleted_at' => null,
+ ]);
+
+ $expiredAsset = Asset::factory()->create([
+ 'purchase_date' => now()->subDays(370)->format('Y-m-d'),
+ 'warranty_months' => 12,
+ 'archived' => 0,
+ 'deleted_at' => null,
+ ]);
+
+ $notExpiringAsset = Asset::factory()->create([
+ 'purchase_date' => now()->subDays(330)->format('Y-m-d'),
+ 'warranty_months' => 12,
+ 'archived' => 0,
+ 'deleted_at' => null,
+ ]);
+
+ $this->artisan('snipeit:expiring-alerts')->assertExitCode(0);
+
+ Mail::assertSent(ExpiringAssetsMail::class, function($mail) use ($alert_email, $expiringAsset) {
+ return $mail->hasTo($alert_email) && $mail->assets->contains($expiringAsset);
+ });
+
+ Mail::assertNotSent(ExpiringAssetsMail::class, function($mail) use ($expiredAsset, $notExpiringAsset) {
+ return $mail->assets->contains($expiredAsset) || $mail->assets->contains($notExpiringAsset);
+ });
+ }
+
+ public function testExpiringLicensesEmailNotification()
+ {
+ $this->markIncompleteIfSqlite();
+ Mail::fake();
+ $this->settings->enableAlertEmail('admin@example.com');
+ $this->settings->setAlertInterval(60);
+
+ $alert_email = Setting::first()->alert_email;
+
+ $expiringLicense = License::factory()->create([
+ 'expiration_date' => now()->addDays(30)->format('Y-m-d'),
+ 'deleted_at' => null,
+ ]);
+
+ $expiredLicense = License::factory()->create([
+ 'expiration_date' => now()->subDays(10)->format('Y-m-d'),
+ 'deleted_at' => null,
+ ]);
+ $notExpiringLicense = License::factory()->create([
+ 'expiration_date' => now()->addMonths(3)->format('Y-m-d'),
+ 'deleted_at' => null,
+ ]);
+
+ $this->artisan('snipeit:expiring-alerts')->assertExitCode(0);
+
+ Mail::assertSent(ExpiringLicenseMail::class, function($mail) use ($alert_email, $expiringLicense) {
+ return $mail->hasTo($alert_email) && $mail->licenses->contains($expiringLicense);
+ });
+
+ Mail::assertNotSent(ExpiringLicenseMail::class, function($mail) use ($expiredLicense, $notExpiringLicense) {
+ return $mail->licenses->contains($expiredLicense) || $mail->licenses->contains($notExpiringLicense);
+ });
+ }
+
+ public function testAuditWarningThresholdEmailNotification()
+ {
+ $this->markIncompleteIfSqlite();
+ Mail::fake();
+ $this->settings->enableAlertEmail('admin@example.com');
+ $this->settings->setAuditWarningDays(15);
+
+ $alert_email = Setting::first()->alert_email;
+
+ $upcomingAuditableAsset = Asset::factory()->create([
+ 'next_audit_date' => now()->addDays(14)->format('Y-m-d'),
+ 'deleted_at' => null,
+ ]);
+
+ $overDueForAuditableAsset = Asset::factory()->create([
+ 'next_audit_date' => now()->subDays(1)->format('Y-m-d'),
+ 'deleted_at' => null,
+ ]);
+
+ $notAuditableAsset = Asset::factory()->create([
+ 'next_audit_date' => now()->addDays(30)->format('Y-m-d'),
+ 'deleted_at' => null,
+ ]);
+
+ $this->artisan('snipeit:upcoming-audits')->assertExitCode(0);
+
+ Mail::assertSent(SendUpcomingAuditMail::class, function($mail) use ($alert_email, $upcomingAuditableAsset, $overDueForAuditableAsset) {
+ return $mail->hasTo($alert_email) && ($mail->assets->contains($upcomingAuditableAsset) && $mail->assets->contains($overDueForAuditableAsset));
+ });
+ Mail::assertNotSent(SendUpcomingAuditMail::class, function($mail) use ($alert_email, $notAuditableAsset) {
+ return $mail->hasTo($alert_email) && $mail->assets->contains($notAuditableAsset);
+ });
+ }
+}
\ No newline at end of file
diff --git a/tests/Feature/Redirects/ModelNotFoundRedirectTest.php b/tests/Feature/Redirects/ModelNotFoundRedirectTest.php
new file mode 100644
index 000000000..26401e960
--- /dev/null
+++ b/tests/Feature/Redirects/ModelNotFoundRedirectTest.php
@@ -0,0 +1,51 @@
+actingAs(User::factory()->viewAssets()->create())
+ ->get(route('hardware.checkout.create', 9999))
+ ->assertRedirectToRoute('hardware.index');
+ }
+
+ public function testHandlesAssetMaintenance404()
+ {
+ $this->actingAs(User::factory()->viewAssets()->create())
+ ->get(route('maintenances.show', 9999))
+ ->assertRedirectToRoute('maintenances.index');
+ }
+
+ public function testHandlesAssetModel404()
+ {
+ $this->actingAs(User::factory()->viewAssetModels()->create())
+ ->get(route('models.show', 9999))
+ ->assertRedirectToRoute('models.index');
+ }
+
+ public function testHandlesLicenseSeat404()
+ {
+ $this->actingAs(User::factory()->viewLicenses()->create())
+ ->get(route('licenses.checkin', 9999))
+ ->assertRedirectToRoute('licenses.index');
+ }
+
+ public function testHandlesPredefinedKit404()
+ {
+ $this->actingAs(User::factory()->viewPredefinedKits()->create())
+ ->get(route('kits.show', 9999))
+ ->assertRedirectToRoute('kits.index');
+ }
+
+ public function testHandlesReportTemplate404()
+ {
+ $this->actingAs(User::factory()->canViewReports()->create())
+ ->get(route('report-templates.show', 9999))
+ ->assertRedirectToRoute('reports/custom');
+ }
+}
diff --git a/tests/Feature/ReportTemplates/DeleteReportTemplateTest.php b/tests/Feature/ReportTemplates/DeleteReportTemplateTest.php
index 2b108443a..cebc8156a 100644
--- a/tests/Feature/ReportTemplates/DeleteReportTemplateTest.php
+++ b/tests/Feature/ReportTemplates/DeleteReportTemplateTest.php
@@ -17,7 +17,7 @@ class DeleteReportTemplateTest extends TestCase implements TestsPermissionsRequi
$this->actingAs(User::factory()->create())
->post(route('report-templates.destroy', $reportTemplate->id))
- ->assertNotFound();
+ ->assertStatus(302);
$this->assertModelExists($reportTemplate);
}
@@ -28,7 +28,7 @@ class DeleteReportTemplateTest extends TestCase implements TestsPermissionsRequi
$this->actingAs(User::factory()->canViewReports()->create())
->delete(route('report-templates.destroy', $reportTemplate->id))
- ->assertNotFound();
+ ->assertStatus(302);
$this->assertModelExists($reportTemplate);
}
diff --git a/tests/Feature/ReportTemplates/EditReportTemplateTest.php b/tests/Feature/ReportTemplates/EditReportTemplateTest.php
index 88363ec8b..d359baddd 100644
--- a/tests/Feature/ReportTemplates/EditReportTemplateTest.php
+++ b/tests/Feature/ReportTemplates/EditReportTemplateTest.php
@@ -15,7 +15,7 @@ class EditReportTemplateTest extends TestCase implements TestsPermissionsRequire
{
$this->actingAs(User::factory()->create())
->get(route('report-templates.edit', ReportTemplate::factory()->create()))
- ->assertNotFound();
+ ->assertStatus(302);
}
public function testCannotLoadEditPageForAnotherUsersReportTemplate()
@@ -25,7 +25,7 @@ class EditReportTemplateTest extends TestCase implements TestsPermissionsRequire
$this->actingAs($user)
->get(route('report-templates.edit', $reportTemplate))
- ->assertNotFound();
+ ->assertStatus(302);
}
public function testCanLoadEditReportTemplatePage()
diff --git a/tests/Feature/ReportTemplates/ShowReportTemplateTest.php b/tests/Feature/ReportTemplates/ShowReportTemplateTest.php
index 3e57db898..748df288b 100644
--- a/tests/Feature/ReportTemplates/ShowReportTemplateTest.php
+++ b/tests/Feature/ReportTemplates/ShowReportTemplateTest.php
@@ -15,7 +15,7 @@ class ShowReportTemplateTest extends TestCase implements TestsPermissionsRequire
{
$this->actingAs(User::factory()->create())
->get(route('report-templates.show', ReportTemplate::factory()->create()))
- ->assertNotFound();
+ ->assertStatus(302);
}
public function testCanLoadASavedReportTemplate()
@@ -38,6 +38,6 @@ class ShowReportTemplateTest extends TestCase implements TestsPermissionsRequire
$this->actingAs(User::factory()->canViewReports()->create())
->get(route('report-templates.show', $reportTemplate))
- ->assertNotFound();
+ ->assertStatus(302);
}
}
diff --git a/tests/Feature/ReportTemplates/UpdateReportTemplateTest.php b/tests/Feature/ReportTemplates/UpdateReportTemplateTest.php
index 9afecb084..09c168b27 100644
--- a/tests/Feature/ReportTemplates/UpdateReportTemplateTest.php
+++ b/tests/Feature/ReportTemplates/UpdateReportTemplateTest.php
@@ -15,14 +15,14 @@ class UpdateReportTemplateTest extends TestCase implements TestsPermissionsRequi
{
$this->actingAs(User::factory()->create())
->post(route('report-templates.update', ReportTemplate::factory()->create()))
- ->assertNotFound();
+ ->assertStatus(302);
}
public function testCannotUpdateAnotherUsersReportTemplate()
{
$this->actingAs(User::factory()->canViewReports()->create())
->post(route('report-templates.update', ReportTemplate::factory()->create()))
- ->assertNotFound();
+ ->assertStatus(302);
}
public function testUpdatingReportTemplateRequiresValidFields()
diff --git a/tests/Feature/Settings/LdapSettingsTest.php b/tests/Feature/Settings/LdapSettingsTest.php
index 317ccb42d..154a44138 100644
--- a/tests/Feature/Settings/LdapSettingsTest.php
+++ b/tests/Feature/Settings/LdapSettingsTest.php
@@ -28,6 +28,7 @@ class LdapSettingsTest extends TestCase
'ldap_basedn' => 'uid=',
'ldap_fname_field' => 'SomeFirstnameField',
'ldap_server' => 'ldaps://ldap.example.com',
+ 'ldap_invert_active_flag' => 0,
]))
->assertStatus(302)
->assertValid('ldap_enabled')
diff --git a/tests/Feature/Settings/ShowSetUpPageTest.php b/tests/Feature/Settings/ShowSetUpPageTest.php
index 196902ac5..fe071491e 100644
--- a/tests/Feature/Settings/ShowSetUpPageTest.php
+++ b/tests/Feature/Settings/ShowSetUpPageTest.php
@@ -21,6 +21,8 @@ use Tests\TestCase;
class ShowSetUpPageTest extends TestCase
{
+
+ static ?TestResponse $latestResponse;
/**
* We do not want to make actual http request on every test to check .env file
* visibility because that can be really slow especially in some cases where an
@@ -34,7 +36,8 @@ class ShowSetUpPageTest extends TestCase
Http::fake([URL::to('.env') => Http::response(null, 404)]);
}
- return $this->get('/setup');
+ self::$latestResponse = $this->get('/setup');
+ return self::$latestResponse;
}
public function testView(): void
diff --git a/tests/Feature/StatusLabels/Ui/ShowStatusLabelTest.php b/tests/Feature/StatusLabels/Ui/ShowStatusLabelTest.php
index 0b7389603..96e8d7552 100644
--- a/tests/Feature/StatusLabels/Ui/ShowStatusLabelTest.php
+++ b/tests/Feature/StatusLabels/Ui/ShowStatusLabelTest.php
@@ -11,7 +11,7 @@ class ShowStatusLabelTest extends TestCase
public function testPageRenders()
{
$this->actingAs(User::factory()->superuser()->create())
- ->get(route('statuslabels.show', Statuslabel::factory()->create()->id))
+ ->get(route('statuslabels.show', Statuslabel::factory()->create()))
->assertOk();
}
}
diff --git a/tests/Feature/Users/Api/DeleteUsersTest.php b/tests/Feature/Users/Api/DeleteUsersTest.php
index 9677e5f7d..a2e43f04b 100644
--- a/tests/Feature/Users/Api/DeleteUsersTest.php
+++ b/tests/Feature/Users/Api/DeleteUsersTest.php
@@ -37,7 +37,7 @@ class DeleteUsersTest extends TestCase implements TestsFullMultipleCompaniesSupp
{
$user = User::factory()->deletedUser()->create();
$this->actingAsForApi(User::factory()->deleteUsers()->create())
- ->deleteJson(route('api.users.destroy', $user->id))
+ ->deleteJson(route('api.users.destroy', $user))
->assertOk()
->assertStatus(200)
->assertStatusMessageIs('error')
@@ -51,7 +51,7 @@ class DeleteUsersTest extends TestCase implements TestsFullMultipleCompaniesSupp
$this->assertFalse($manager->isDeletable());
$this->actingAsForApi(User::factory()->deleteUsers()->create())
- ->deleteJson(route('api.users.destroy', $manager->id))
+ ->deleteJson(route('api.users.destroy', $manager))
->assertOk()
->assertStatus(200)
->assertStatusMessageIs('error')
@@ -66,7 +66,7 @@ class DeleteUsersTest extends TestCase implements TestsFullMultipleCompaniesSupp
$this->assertFalse($manager->isDeletable());
$this->actingAsForApi(User::factory()->deleteUsers()->create())
- ->deleteJson(route('api.users.destroy', $manager->id))
+ ->deleteJson(route('api.users.destroy', $manager))
->assertOk()
->assertStatus(200)
->assertStatusMessageIs('error')
@@ -81,7 +81,7 @@ class DeleteUsersTest extends TestCase implements TestsFullMultipleCompaniesSupp
$this->assertFalse($manager->isDeletable());
$this->actingAsForApi(User::factory()->deleteUsers()->create())
- ->deleteJson(route('api.users.destroy', $manager->id))
+ ->deleteJson(route('api.users.destroy', $manager))
->assertOk()
->assertStatus(200)
->assertStatusMessageIs('error')
@@ -111,7 +111,7 @@ class DeleteUsersTest extends TestCase implements TestsFullMultipleCompaniesSupp
$userFromB = User::factory()->deleteUsers()->for($companyB)->create();
$this->actingAsForApi($userFromA)
- ->deleteJson(route('api.users.destroy', ['user' => $userFromB->id]))
+ ->deleteJson(route('api.users.destroy', $userFromB))
->assertOk()
->assertStatus(200)
->assertStatusMessageIs('error')
@@ -121,7 +121,7 @@ class DeleteUsersTest extends TestCase implements TestsFullMultipleCompaniesSupp
$this->assertNull($userFromB->deleted_at);
$this->actingAsForApi($userFromB)
- ->deleteJson(route('api.users.destroy', ['user' => $userFromA->id]))
+ ->deleteJson(route('api.users.destroy', $userFromA))
->assertOk()
->assertStatus(200)
->assertStatusMessageIs('error')
@@ -131,7 +131,7 @@ class DeleteUsersTest extends TestCase implements TestsFullMultipleCompaniesSupp
$this->assertNull($userFromA->deleted_at);
$this->actingAsForApi($superuser)
- ->deleteJson(route('api.users.destroy', ['user' => $userFromA->id]))
+ ->deleteJson(route('api.users.destroy', $userFromA))
->assertOk()
->assertStatus(200)
->assertStatusMessageIs('success')
diff --git a/tests/Feature/Users/Api/StoreUsersTest.php b/tests/Feature/Users/Api/StoreUsersTest.php
new file mode 100644
index 000000000..41cb04e3c
--- /dev/null
+++ b/tests/Feature/Users/Api/StoreUsersTest.php
@@ -0,0 +1,78 @@
+actingAsForApi(User::factory()->create())
+ ->postJson(route('api.users.store'), [
+ 'first_name' => 'Joe',
+ 'username' => 'joe',
+ 'password' => 'joe_password',
+ 'password_confirmation' => 'joe_password',
+ ])
+ ->assertForbidden();
+ }
+
+ public function testCompanyIdNeedsToBeInteger()
+ {
+ $company = Company::factory()->create();
+
+ $this->actingAsForApi(User::factory()->createUsers()->create())
+ ->postJson(route('api.users.store'), [
+ 'company_id' => [$company->id],
+ 'first_name' => 'Joe',
+ 'username' => 'joe',
+ 'password' => 'joe_password',
+ 'password_confirmation' => 'joe_password',
+ ])
+ ->assertStatusMessageIs('error')
+ ->assertJson(function (AssertableJson $json) {
+ $json->has('messages.company_id')->etc();
+ });
+ }
+
+ public function testDepartmentIdNeedsToBeInteger()
+ {
+ $department = Department::factory()->create();
+
+ $this->actingAsForApi(User::factory()->createUsers()->create())
+ ->postJson(route('api.users.store'), [
+ 'department_id' => [$department->id],
+ 'first_name' => 'Joe',
+ 'username' => 'joe',
+ 'password' => 'joe_password',
+ 'password_confirmation' => 'joe_password',
+ ])
+ ->assertStatusMessageIs('error')
+ ->assertJson(function (AssertableJson $json) {
+ $json->has('messages.department_id')->etc();
+ });
+ }
+
+ public function testCanStoreUser()
+ {
+ $this->actingAsForApi(User::factory()->createUsers()->create())
+ ->postJson(route('api.users.store'), [
+ 'first_name' => 'Darth',
+ 'username' => 'darthvader',
+ 'password' => 'darth_password',
+ 'password_confirmation' => 'darth_password',
+ ])
+ ->assertStatusMessageIs('success')
+ ->assertOk();
+
+ $this->assertDatabaseHas('users', [
+ 'first_name' => 'Darth',
+ 'username' => 'darthvader',
+ ]);
+ }
+}
diff --git a/tests/Feature/Users/Ui/CloneUserTest.php b/tests/Feature/Users/Ui/CloneUserTest.php
new file mode 100644
index 000000000..97b4babd7
--- /dev/null
+++ b/tests/Feature/Users/Ui/CloneUserTest.php
@@ -0,0 +1,18 @@
+actingAs(User::factory()->superuser()->create())
+ ->get(route('users.clone.show', User::factory()->create()))
+ ->assertOk();
+ }
+
+
+}
diff --git a/tests/Feature/Users/Ui/CreateUserTest.php b/tests/Feature/Users/Ui/CreateUserTest.php
index 63d27d3d1..456190ee3 100644
--- a/tests/Feature/Users/Ui/CreateUserTest.php
+++ b/tests/Feature/Users/Ui/CreateUserTest.php
@@ -9,8 +9,12 @@ class CreateUserTest extends TestCase
{
public function testPageRenders()
{
- $this->actingAs(User::factory()->superuser()->create())
+ $admin = User::factory()->createUsers()->create();
+ $response = $this->actingAs(User::factory()->superuser()->create())
->get(route('users.create'))
->assertOk();
+ $response->assertDontSee($admin->first_name);
+ $response->assertDontSee($admin->last_name);
+ $response->assertDontSee($admin->email);
}
}
diff --git a/tests/Feature/Users/Ui/DeleteUserTest.php b/tests/Feature/Users/Ui/DeleteUserTest.php
index da4c5a37e..6d687ed0f 100644
--- a/tests/Feature/Users/Ui/DeleteUserTest.php
+++ b/tests/Feature/Users/Ui/DeleteUserTest.php
@@ -48,6 +48,14 @@ class DeleteUserTest extends TestCase
$this->followRedirects($response)->assertSee(trans('general.error'));
}
+ public function testCanViewSoftDeletedUser()
+ {
+ $user = User::factory()->deletedUser()->viewUsers()->create();
+ $response = $this->actingAs(User::factory()->deleteUsers()->viewUsers()->create())
+ ->get(route('users.show', $user->id))
+ ->assertStatus(200);
+ }
+
public function testFmcsPermissionsToDeleteUser()
{
diff --git a/tests/Feature/Users/Ui/EmailAssignedToUserTest.php b/tests/Feature/Users/Ui/EmailAssignedToUserTest.php
new file mode 100644
index 000000000..a6403679c
--- /dev/null
+++ b/tests/Feature/Users/Ui/EmailAssignedToUserTest.php
@@ -0,0 +1,40 @@
+settings->enableMultipleFullCompanySupport();
+
+ [$companyA, $companyB] = Company::factory()->count(2)->create();
+
+ $superuser = User::factory()->superuser()->create();
+ $user = User::factory()->for($companyB)->create();
+
+ $this->actingAs(User::factory()->viewUsers()->for($companyA)->create())
+ ->post(route('users.email', ['userId' => $user->id]))
+ ->assertStatus(403);
+
+ $this->actingAs(User::factory()->viewUsers()->for($companyB)->create())
+ ->post(route('users.email', ['userId' => $user->id]))
+ ->assertStatus(302);
+
+ $this->actingAs($superuser)
+ ->post(route('users.email', ['userId' => $user->id]))
+ ->assertStatus(302);
+
+ Notification::assertSentTo(
+ [$user], CurrentInventory::class
+ );
+ }
+}
diff --git a/tests/Feature/Users/Ui/PrintUserInventoryTest.php b/tests/Feature/Users/Ui/PrintUserInventoryTest.php
new file mode 100644
index 000000000..4de7c7cdd
--- /dev/null
+++ b/tests/Feature/Users/Ui/PrintUserInventoryTest.php
@@ -0,0 +1,41 @@
+actingAs(User::factory()->create())
+ ->get(route('users.print', User::factory()->create()))
+ ->assertStatus(403);
+ }
+
+ public function testCanPrintUserInventory()
+ {
+ $actor = User::factory()->viewUsers()->create();
+
+ $this->actingAs($actor)
+ ->get(route('users.print', User::factory()->create()))
+ ->assertOk()
+ ->assertStatus(200);
+ }
+
+ public function testCannotPrintUserInventoryFromAnotherCompany()
+ {
+ $this->settings->enableMultipleFullCompanySupport();
+
+ [$companyA, $companyB] = Company::factory()->count(2)->create();
+
+ $actor = User::factory()->for($companyA)->viewUsers()->create();
+ $user = User::factory()->for($companyB)->create();
+
+ $this->actingAs($actor)
+ ->get(route('users.print', $user))
+ ->assertStatus(302);
+ }
+}
diff --git a/tests/Feature/Users/Ui/ViewUserTest.php b/tests/Feature/Users/Ui/ViewUserTest.php
index 791c80489..ca45601ff 100644
--- a/tests/Feature/Users/Ui/ViewUserTest.php
+++ b/tests/Feature/Users/Ui/ViewUserTest.php
@@ -4,80 +4,38 @@ namespace Tests\Feature\Users\Ui;
use App\Models\Company;
use App\Models\User;
-use App\Notifications\CurrentInventory;
-use Illuminate\Support\Facades\Notification;
use Tests\TestCase;
class ViewUserTest extends TestCase
{
- public function testPermissionsForUserDetailPage()
+ public function testRequiresPermissionToViewUser()
{
- $this->settings->enableMultipleFullCompanySupport();
-
- [$companyA, $companyB] = Company::factory()->count(2)->create();
-
- $superuser = User::factory()->superuser()->create();
- $user = User::factory()->for($companyB)->create();
-
- $this->actingAs(User::factory()->editUsers()->for($companyA)->create())
- ->get(route('users.show', ['user' => $user->id]))
+ $this->actingAs(User::factory()->create())
+ ->get(route('users.show', User::factory()->create()))
->assertStatus(403);
+ }
- $this->actingAs($superuser)
- ->get(route('users.show', ['user' => $user->id]))
+ public function testCanViewUser()
+ {
+ $actor = User::factory()->viewUsers()->create();
+
+ $this->actingAs($actor)
+ ->get(route('users.show', User::factory()->create()))
->assertOk()
->assertStatus(200);
}
- public function testPermissionsForPrintAllInventoryPage()
+ public function testCannotViewUserFromAnotherCompany()
{
$this->settings->enableMultipleFullCompanySupport();
[$companyA, $companyB] = Company::factory()->count(2)->create();
- $superuser = User::factory()->superuser()->create();
+ $actor = User::factory()->for($companyA)->viewUsers()->create();
$user = User::factory()->for($companyB)->create();
- $this->actingAs(User::factory()->viewUsers()->for($companyA)->create())
- ->get(route('users.print', ['userId' => $user->id]))
+ $this->actingAs($actor)
+ ->get(route('users.show', $user))
->assertStatus(302);
-
- $this->actingAs(User::factory()->viewUsers()->for($companyB)->create())
- ->get(route('users.print', ['userId' => $user->id]))
- ->assertStatus(200);
-
- $this->actingAs($superuser)
- ->get(route('users.print', ['userId' => $user->id]))
- ->assertOk()
- ->assertStatus(200);
- }
-
- public function testUserWithoutCompanyPermissionsCannotSendInventory()
- {
-
- Notification::fake();
-
- $this->settings->enableMultipleFullCompanySupport();
-
- [$companyA, $companyB] = Company::factory()->count(2)->create();
-
- $superuser = User::factory()->superuser()->create();
- $user = User::factory()->for($companyB)->create();
-
- $this->actingAs(User::factory()->viewUsers()->for($companyA)->create())
- ->post(route('users.email', ['userId' => $user->id]))
- ->assertStatus(403);
-
- $this->actingAs(User::factory()->viewUsers()->for($companyB)->create())
- ->post(route('users.email', ['userId' => $user->id]))
- ->assertStatus(302);
-
- $this->actingAs($superuser)
- ->post(route('users.email', ['userId' => $user->id]))
- ->assertStatus(302);
-
- Notification::assertSentTo(
- [$user], CurrentInventory::class
- );
}
}
diff --git a/tests/Support/Importing/FileBuilder.php b/tests/Support/Importing/FileBuilder.php
index fad40054b..bf08dc96d 100644
--- a/tests/Support/Importing/FileBuilder.php
+++ b/tests/Support/Importing/FileBuilder.php
@@ -206,7 +206,7 @@ abstract class FileBuilder
*
* @return string The filename.
*/
- public function saveToImportsDirectory(?string $filename = null): string
+ public function saveToImportsDirectory(?string $filename = null, ?string $locale = null): string
{
$filename ??= Str::random(40) . '.csv';
@@ -214,9 +214,15 @@ abstract class FileBuilder
$stream = fopen(config('app.private_uploads') . "/imports/{$filename}", 'w');
foreach ($this->toCsv() as $row) {
+ if ($locale) {
+ $newrow = [];
+ foreach ($row as $index => $cell) {
+ $newrow[$index] = iconv('utf-8', $locale, (string) $cell);
+ }
+ $row = $newrow;
+ }
fputcsv($stream, $row);
}
-
return $filename;
} finally {
if (is_resource($stream)) {
diff --git a/tests/Support/Settings.php b/tests/Support/Settings.php
index e171c0ab9..8680d1c3b 100644
--- a/tests/Support/Settings.php
+++ b/tests/Support/Settings.php
@@ -21,12 +21,29 @@ class Settings
public function enableAlertEmail(string $email = 'notifications@afcrichmond.com'): Settings
{
- return $this->update(['alert_email' => $email]);
+ return $this->update([
+ 'alert_email' => $email,
+ 'alerts_enabled' => 1,
+ ]);
+ }
+ public function setAlertInterval(int $days): Settings
+ {
+ return $this->update([
+ 'alert_threshold' => $days,
+ ]);
+ }
+ public function setAuditWarningDays(int $days): Settings
+ {
+ return $this->update([
+ 'audit_warning_days' => $days,
+ ]);
}
-
public function disableAlertEmail(): Settings
{
- return $this->update(['alert_email' => null]);
+ return $this->update([
+ 'alert_email' => null,
+ 'alerts_enabled' => 0,
+ ]);
}
public function enableMultipleFullCompanySupport(): Settings
diff --git a/tests/Unit/ActionLogTransformer/ReportTemplateActionLogTransformerTest.php b/tests/Unit/ActionLogTransformer/ReportTemplateActionLogTransformerTest.php
deleted file mode 100644
index e4d594b49..000000000
--- a/tests/Unit/ActionLogTransformer/ReportTemplateActionLogTransformerTest.php
+++ /dev/null
@@ -1,71 +0,0 @@
-create();
-
- $actionLogs = Actionlog::query()
- ->whereMorphedTo('item', ReportTemplate::class)
- ->where('action_type', 'create')
- ->get();
-
- // should be created when ActionLog is created
- $this->assertCount(1, $actionLogs);
-
- $results = (new ActionlogsTransformer())->transformActionlogs($actionLogs, 10);
-
- $this->assertArrayHasKey('rows', $results);
- $this->assertCount(1, $results['rows']);
- }
-
- public function testLogEntryForUpdatingReportTemplateCanBeDisplayedTransformed()
- {
- $reportTemplate = ReportTemplate::factory()->create();
-
- $reportTemplate->update([
- 'options' => ['new' => 'value']
- ]);
-
- $actionLogs = Actionlog::query()
- ->whereMorphedTo('item', ReportTemplate::class)
- ->where('action_type', 'update')
- ->get();
-
- $this->assertCount(1, $actionLogs);
-
- $results = (new ActionlogsTransformer())->transformActionlogs($actionLogs, 10);
-
- $this->assertArrayHasKey('rows', $results);
- $this->assertCount(1, $results['rows']);
- }
-
- public function testLogEntryForDeletingReportTemplateCanBeDisplayedTransformed()
- {
- $reportTemplate = ReportTemplate::factory()->create();
-
- $reportTemplate->delete();
-
- $actionLogs = Actionlog::query()
- ->whereMorphedTo('item', ReportTemplate::class)
- ->where('action_type', 'delete')
- ->get();
-
- $this->assertCount(1, $actionLogs);
-
- $results = (new ActionlogsTransformer())->transformActionlogs($actionLogs, 10);
-
- $this->assertArrayHasKey('rows', $results);
- $this->assertCount(1, $results['rows']);
- }
-}
diff --git a/tests/Unit/LdapTest.php b/tests/Unit/LdapTest.php
index f3bc52163..a492b1efb 100644
--- a/tests/Unit/LdapTest.php
+++ b/tests/Unit/LdapTest.php
@@ -2,6 +2,7 @@
namespace Tests\Unit;
+use App\Models\Setting;
use PHPUnit\Framework\Attributes\Group;
use App\Models\Ldap;
use Tests\TestCase;
@@ -206,4 +207,30 @@ class LdapTest extends TestCase
$this->assertEqualsCanonicalizing(["count" => 2], $results);
}
+ public function testNonexistentTLSFile()
+ {
+ $this->settings->enableLdap()->set(['ldap_client_tls_cert' => 'SAMPLE CERT TEXT']);
+ $certfile = Setting::get_client_side_cert_path();
+ $this->assertStringEqualsFile($certfile, 'SAMPLE CERT TEXT');
+ }
+
+ public function testStaleTLSFile()
+ {
+ file_put_contents(Setting::get_client_side_cert_path(), 'STALE CERT FILE');
+ sleep(1); // FIXME - this is going to slow down tests
+ $this->settings->enableLdap()->set(['ldap_client_tls_cert' => 'SAMPLE CERT TEXT']);
+ $certfile = Setting::get_client_side_cert_path();
+ $this->assertStringEqualsFile($certfile, 'SAMPLE CERT TEXT');
+ }
+
+ public function testFreshTLSFile()
+ {
+ $this->settings->enableLdap()->set(['ldap_client_tls_cert' => 'SAMPLE CERT TEXT']);
+ $client_side_cert_path = Setting::get_client_side_cert_path();
+ file_put_contents($client_side_cert_path, 'WEIRDLY UPDATED CERT FILE');
+ //the system should respect our cache-file, since the settings haven't been updated
+ $possibly_recached_cert_file = Setting::get_client_side_cert_path(); //this should *NOT* re-cache from the Settings
+ $this->assertStringEqualsFile($possibly_recached_cert_file, 'WEIRDLY UPDATED CERT FILE');
+ }
+
}
diff --git a/tests/Unit/Listeners/LogListenerTest.php b/tests/Unit/Listeners/LogListenerTest.php
index 5b0813c28..64dd2a3d6 100644
--- a/tests/Unit/Listeners/LogListenerTest.php
+++ b/tests/Unit/Listeners/LogListenerTest.php
@@ -3,7 +3,6 @@
namespace Tests\Unit\Listeners;
use App\Events\CheckoutableCheckedOut;
-use App\Events\NoteAdded;
use App\Listeners\LogListener;
use App\Models\Asset;
use App\Models\User;
@@ -38,19 +37,4 @@ class LogListenerTest extends TestCase
]);
}
- public function testLogsEntryOnAssetNoteCreation()
- {
- $asset = Asset::factory()->create();
- $noteAddedBy = User::factory()->create();
-
- event(new NoteAdded($asset, $noteAddedBy, 'My Cool Note!'));
-
- $this->assertDatabaseHas('action_logs', [
- 'action_type' => 'note_added',
- 'created_by' => $noteAddedBy->id,
- 'item_id' => $asset->id,
- 'item_type' => Asset::class,
- 'note' => 'My Cool Note!',
- ]);
- }
}
diff --git a/tests/Unit/Mail/CheckoutAssetMailTest.php b/tests/Unit/Mail/CheckoutAssetMailTest.php
new file mode 100644
index 000000000..af198b2cd
--- /dev/null
+++ b/tests/Unit/Mail/CheckoutAssetMailTest.php
@@ -0,0 +1,75 @@
+ [
+ function () {
+ $asset = Asset::factory()->requiresAcceptance()->create();
+ return [
+ 'asset' => $asset,
+ 'acceptance' => CheckoutAcceptance::factory()->for($asset, 'checkoutable')->create(),
+ 'first_time_sending' => true,
+ 'expected_subject' => 'Asset checked out',
+ 'expected_opening' => 'A new item has been checked out under your name that requires acceptance, details are below.'
+ ];
+ }
+ ];
+
+ yield 'Asset not requiring acceptance' => [
+ function () {
+ return [
+ 'asset' => Asset::factory()->doesNotRequireAcceptance()->create(),
+ 'acceptance' => null,
+ 'first_time_sending' => true,
+ 'expected_subject' => 'Asset checked out',
+ 'expected_opening' => 'A new item has been checked out under your name, details are below.'
+ ];
+ }
+ ];
+
+ yield 'Reminder' => [
+ function () {
+ return [
+ 'asset' => Asset::factory()->requiresAcceptance()->create(),
+ 'acceptance' => CheckoutAcceptance::factory()->create(),
+ 'first_time_sending' => false,
+ 'expected_subject' => 'Reminder: You have Unaccepted Assets.',
+ 'expected_opening' => 'An item was recently checked out under your name that requires acceptance, details are below.'
+ ];
+ }
+ ];
+ }
+
+ #[DataProvider('data')]
+ public function testSubjectLineAndOpening($data)
+ {
+ [
+ 'asset' => $asset,
+ 'acceptance' => $acceptance,
+ 'first_time_sending' => $firstTimeSending,
+ 'expected_subject' => $expectedSubject,
+ 'expected_opening' => $expectedOpening,
+ ] = $data();
+
+ (new CheckoutAssetMail(
+ $asset,
+ User::factory()->create(),
+ User::factory()->create(),
+ $acceptance,
+ 'A note goes here',
+ $firstTimeSending,
+ ))->assertHasSubject($expectedSubject)
+ ->assertSeeInText($expectedOpening);
+ }
+}
diff --git a/tests/Unit/Models/ReportTemplates/ReportTemplateActivityLoggingTest.php b/tests/Unit/Models/ReportTemplates/ReportTemplateActivityLoggingTest.php
deleted file mode 100644
index e30a7e2c7..000000000
--- a/tests/Unit/Models/ReportTemplates/ReportTemplateActivityLoggingTest.php
+++ /dev/null
@@ -1,100 +0,0 @@
-create();
- $this->actingAs($user);
-
- $reportTemplate = ReportTemplate::factory()->create();
-
- $this->assertDatabaseHas('action_logs', [
- 'created_by' => $user->id,
- 'action_type' => 'create',
- 'target_id' => null,
- 'target_type' => null,
- 'item_type' => ReportTemplate::class,
- 'item_id' => $reportTemplate->id,
- ]);
- }
-
- public function testUpdatingReportTemplateIsLogged()
- {
- $user = User::factory()->create();
- $this->actingAs($user);
-
- $reportTemplate = ReportTemplate::factory()->create([
- 'name' => 'Name A',
- 'options' => [
- 'company' => '1',
- 'location' => '1',
- 'by_company_id' => ['1'],
- 'by_location_id' => ['17'],
- ],
- ]);
-
- $reportTemplate->update([
- 'name' => 'Another Name',
- 'options' => [
- 'company' => '1',
- 'by_company_id' => ['1'],
- ],
- ]);
-
- $this->assertDatabaseHas('action_logs', [
- 'created_by' => $user->id,
- 'action_type' => 'update',
- 'target_id' => null,
- 'target_type' => null,
- 'item_type' => ReportTemplate::class,
- 'item_id' => $reportTemplate->id,
- 'log_meta' => json_encode([
- 'name' => [
- 'old' => 'Name A',
- 'new' => 'Another Name'
- ],
- 'options' => [
- 'old' => [
- 'company' => '1',
- 'location' => '1',
- 'by_company_id' => ['1'],
- 'by_location_id' => ['17'],
- ],
- 'new' => [
- 'company' => '1',
- 'by_company_id' => ['1'],
- ],
- ],
- ]),
- ]);
- }
-
- public function testDeletingReportTemplateIsLogged()
- {
- $user = User::factory()->create();
- $this->actingAs($user);
-
- $reportTemplate = ReportTemplate::factory()->create();
-
- $reportTemplate->delete();
-
- $this->assertDatabaseHas('action_logs', [
- 'created_by' => $user->id,
- 'action_type' => 'delete',
- 'target_id' => null,
- 'target_type' => null,
- 'item_type' => ReportTemplate::class,
- 'item_id' => $reportTemplate->id,
- ]);
- }
-}
diff --git a/tests/Unit/NotificationTest.php b/tests/Unit/NotificationTest.php
index 3d5b3c5a7..5b420a675 100644
--- a/tests/Unit/NotificationTest.php
+++ b/tests/Unit/NotificationTest.php
@@ -7,9 +7,7 @@ use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Category;
use Carbon\Carbon;
-use App\Notifications\CheckoutAssetNotification;
use Illuminate\Support\Facades\Mail;
-use Illuminate\Support\Facades\Notification;
use Tests\TestCase;
class NotificationTest extends TestCase
@@ -33,8 +31,8 @@ class NotificationTest extends TestCase
Mail::fake();
$asset->checkOut($user, $admin->id);
- Mail::assertSent(CheckoutAssetMail::class, function ($mail) use ($user) {
- return $mail->hasTo($user->email);
+ Mail::assertSent(CheckoutAssetMail::class, function (CheckoutAssetMail $mail) use ($user) {
+ return $mail->hasTo($user->email) && $mail->hasSubject(trans('mail.Asset_Checkout_Notification'));
});
}
public function testDefaultEulaIsSentWhenSetInCategory()
diff --git a/tests/Unit/Transformers/DepreciationReportTransformerTest.php b/tests/Unit/Transformers/DepreciationReportTransformerTest.php
new file mode 100644
index 000000000..7a0017196
--- /dev/null
+++ b/tests/Unit/Transformers/DepreciationReportTransformerTest.php
@@ -0,0 +1,24 @@
+create();
+ $depreciation = Depreciation::factory()->create(['months' => 0]);
+ $asset->model->depreciation()->associate($depreciation);
+
+ $transformer = new DepreciationReportTransformer;
+
+ $result = $transformer->transformAsset($asset);
+
+ $this->assertIsArray($result);
+ }
+}
diff --git a/tests/Unit/UserTest.php b/tests/Unit/UserTest.php
index e089fc402..9554085c0 100644
--- a/tests/Unit/UserTest.php
+++ b/tests/Unit/UserTest.php
@@ -24,6 +24,14 @@ class UserTest extends TestCase
$this->assertEquals($expected_username, $user['username']);
}
+ public function testFirstNameEmail()
+ {
+ $fullname = "Natalia Allanovna Romanova-O'Shostakova";
+ $expected_email = 'natalia@example.com';
+ $user = User::generateFormattedNameFromFullName($fullname, 'firstname');
+ $this->assertEquals($expected_email, $user['username'] . '@example.com');
+ }
+
public function testFirstNameDotLastName()
{
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
@@ -32,6 +40,14 @@ class UserTest extends TestCase
$this->assertEquals($expected_username, $user['username']);
}
+ public function testFirstNameDotLastNameEmail()
+ {
+ $fullname = "Natalia Allanovna Romanova-O'Shostakova";
+ $expected_email = 'natalia.allanovna-romanova-oshostakova@example.com';
+ $user = User::generateFormattedNameFromFullName($fullname, 'firstname.lastname');
+ $this->assertEquals($expected_email, $user['username'] . '@example.com');
+ }
+
public function testLastNameFirstInitial()
{
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
@@ -40,6 +56,14 @@ class UserTest extends TestCase
$this->assertEquals($expected_username, $user['username']);
}
+ public function testLastNameFirstInitialEmail()
+ {
+ $fullname = "Natalia Allanovna Romanova-O'Shostakova";
+ $expected_email = 'allanovna-romanova-oshostakovan@example.com';
+ $user = User::generateFormattedNameFromFullName($fullname, 'lastnamefirstinitial');
+ $this->assertEquals($expected_email, $user['username'] . '@example.com');
+ }
+
public function testFirstInitialLastName()
{
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
@@ -48,6 +72,14 @@ class UserTest extends TestCase
$this->assertEquals($expected_username, $user['username']);
}
+ public function testFirstInitialLastNameEmail()
+ {
+ $fullname = "Natalia Allanovna Romanova-O'Shostakova";
+ $expected_email = 'nallanovna-romanova-oshostakova@example.com';
+ $user = User::generateFormattedNameFromFullName($fullname, 'filastname');
+ $this->assertEquals($expected_email, $user['username'] . '@example.com');
+ }
+
public function testFirstInitialUnderscoreLastName()
{
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
@@ -56,6 +88,14 @@ class UserTest extends TestCase
$this->assertEquals($expected_username, $user['username']);
}
+ public function testFirstInitialUnderscoreLastNameEmail()
+ {
+ $fullname = "Natalia Allanovna Romanova-O'Shostakova";
+ $expected_email = 'nallanovna-romanova-oshostakova@example.com';
+ $user = User::generateFormattedNameFromFullName($fullname, 'firstinitial_lastname');
+ $this->assertEquals($expected_email, $user['username'] . '@example.com');
+ }
+
public function testSingleName()
{
$fullname = 'Natalia';
@@ -64,35 +104,91 @@ class UserTest extends TestCase
$this->assertEquals($expected_username, $user['username']);
}
- public function firstInitialDotLastname()
+ public function testSingleNameEmail()
+ {
+ $fullname = 'Natalia';
+ $expected_email = 'natalia@example.com';
+ $user = User::generateFormattedNameFromFullName($fullname, 'firstname_lastname',);
+ $this->assertEquals($expected_email, $user['username'] . '@example.com');
+ }
+
+ public function testFirstInitialDotLastname()
{
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
- $expected_username = 'n.allanovnaromanovaoshostakova';
+ $expected_username = 'nallanovna-romanova-oshostakova';
$user = User::generateFormattedNameFromFullName($fullname, 'firstinitial.lastname');
$this->assertEquals($expected_username, $user['username']);
}
- public function lastNameUnderscoreFirstInitial()
+ public function testFirstInitialDotLastnameEmail()
{
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
- $expected_username = 'allanovnaromanovaoshostakova_n';
+ $expected_email = 'nallanovna-romanova-oshostakova@example.com';
+ $user = User::generateFormattedNameFromFullName($fullname, 'firstinitial.lastname');
+ $this->assertEquals($expected_email, $user['username'] . '@example.com');
+ }
+
+ public function testLastNameDotFirstInitial()
+ {
+ $fullname = "Natalia Allanovna Romanova-O'Shostakova";
+ $expected_username = 'allanovna-romanova-oshostakova.n';
+ $user = User::generateFormattedNameFromFullName($fullname, 'lastname.firstinitial');
+ $this->assertEquals($expected_username, $user['username']);
+ }
+
+ public function testLastNameDotFirstInitialEmail()
+ {
+ $fullname = "Natalia Allanovna Romanova-O'Shostakova";
+ $expected_email = 'allanovna-romanova-oshostakova.n@example.com';
+ $user = User::generateFormattedNameFromFullName($fullname, 'lastname.firstinitial');
+ $this->assertEquals($expected_email, $user['username'] . '@example.com');
+ }
+
+ public function testLastNameUnderscoreFirstInitial()
+ {
+ $fullname = "Natalia Allanovna Romanova-O'Shostakova";
+ $expected_username = 'allanovna-romanova-oshostakova_n';
$user = User::generateFormattedNameFromFullName($fullname, 'lastname_firstinitial');
$this->assertEquals($expected_username, $user['username']);
}
- public function firstNameLastName()
+ public function testLastNameUnderscoreFirstInitialEmail()
{
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
- $expected_username = 'nataliaallanovnaromanovaoshostakova';
+ $expected_email = 'allanovna-romanova-oshostakova_n@example.com';
+ $user = User::generateFormattedNameFromFullName($fullname, 'lastname_firstinitial');
+ $this->assertEquals($expected_email, $user['username'] . '@example.com');
+ }
+
+ public function testFirstNameLastName()
+ {
+ $fullname = "Natalia Allanovna Romanova-O'Shostakova";
+ $expected_username = 'nataliaallanovna-romanova-oshostakova';
$user = User::generateFormattedNameFromFullName($fullname, 'firstnamelastname');
$this->assertEquals($expected_username, $user['username']);
}
- public function firstNameLastInitial()
+ public function testFirstNameLastNameEmail()
+ {
+ $fullname = "Natalia Allanovna Romanova-O'Shostakova";
+ $expected_email = 'nataliaallanovna-romanova-oshostakova@example.com';
+ $user = User::generateFormattedNameFromFullName($fullname, 'firstnamelastname');
+ $this->assertEquals($expected_email, $user['username'] . '@example.com');
+ }
+
+ public function testFirstNameLastInitial()
{
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
$expected_username = 'nataliaa';
$user = User::generateFormattedNameFromFullName($fullname, 'firstnamelastinitial');
$this->assertEquals($expected_username, $user['username']);
}
+
+ public function testFirstNameLastInitialEmail()
+ {
+ $fullname = "Natalia Allanovna Romanova-O'Shostakova";
+ $expected_email = 'nataliaa@example.com';
+ $user = User::generateFormattedNameFromFullName($fullname, 'firstnamelastinitial');
+ $this->assertEquals($expected_email, $user['username'] . '@example.com');
+ }
}
diff --git a/upgrade.php b/upgrade.php
index 4d67d24f2..0cca36fcb 100644
--- a/upgrade.php
+++ b/upgrade.php
@@ -1,4 +1,9 @@
1){
@@ -46,6 +52,9 @@ if ($argc > 1){
case '--skip-php-compatibility-checks':
$skip_php_checks = true;
break;
+ case '--skip-backup':
+ $skip_backup = true;
+ break;
case '--branch':
$arg++;
$branch = $argv[$arg];
@@ -62,7 +71,7 @@ if ($argc > 1){
}
}
-echo "--------------------------------------------------------\n";
+echo "\e[95m--------------------------------------------------------\n";
echo "WELCOME TO THE SNIPE-IT UPGRADER! \n";
echo "--------------------------------------------------------\n\n";
echo "This script will attempt to: \n\n";
@@ -71,8 +80,9 @@ echo "- check your PHP version and extension requirements \n";
echo "- check directory permissions \n";
echo "- do a git pull to bring you to the latest version \n";
echo "- run composer install to get your vendors up to date \n";
+echo "- run a backup \n";
echo "- run migrations to get your schema up to date \n";
-echo "- clear out old cache settings\n\n";
+echo "- clear out old cache settings\e[39m\n\n";
// Fetching most current upgrade requirements from github. Read more here: https://github.com/snipe/snipe-it/pull/14127
@@ -81,29 +91,29 @@ $upgrade_requirements_raw = url_get_contents($remote_requirements_file);
$upgrade_requirements = json_decode($upgrade_requirements_raw, true);
if (! $upgrade_requirements) {
if(!$skip_php_checks){
- echo "\nERROR: Failed to retrieve remote requirements from $remote_requirements_file\n\n";
+ echo "\n\e[91mERROR: Failed to retrieve remote requirements from $remote_requirements_file \e[39m\n\n";
if ($branch_override){
- echo "NOTE: You passed a custom branch: $branch\n";
- echo " If the above URL doesn't work, that may be why. Please check you branch spelling/existence\n\n";
+ echo "\e[93mNOTE: You passed a custom branch: $branch\n";
+ echo "If the above URL doesn't work, that may be why. Please check the branch spelling/existence\e[39m\n\n";
}
if (json_last_error()) {
- print "JSON DECODE ERROR DETECTED:\n";
+ print "\e[91mJSON DECODE ERROR DETECTED:\n";
print json_last_error_msg() . "\n\n";
print "Raw curl output:\n";
- print $upgrade_requirements_raw . "\n\n";
+ print $upgrade_requirements_raw . "\e[39m\n\n";
}
- echo "We suggest correcting this, but if you can't, please verify that your requirements conform to those at that url.\n\n";
- echo " -- DANGER -- DO AT YOUR OWN RISK --\n";
- echo " IF YOU ARE SURE, re-run this script with --skip-php-compatibility-checks to skip this check.\n";
- echo " -- DANGER -- THIS COULD BREAK YOUR INSTALLATION";
- die("Exiting.\n\n");
+ echo "\e[93mWe suggest correcting this, but if you can't, please verify that your requirements conform to those at that url.\n\n";
+ echo "\e[91m-- DANGER -- DO AT YOUR OWN RISK --\n";
+ echo "IF YOU ARE SURE, re-run this script with --skip-php-compatibility-checks to skip this check.\n";
+ echo "-- DANGER -- THIS COULD BREAK YOUR INSTALLATION\e[39m\n\n";
+ die("Aborting upgrade.\n\n");
}
- echo "NOTICE: Unable to fetch upgrade requirements, but continuing because you passed --skip-php-compatibility-checks...\n";
+ echo "\e[93mNOTICE: Unable to fetch upgrade requirements, but continuing because you passed --skip-php-compatibility-checks...e[39m\n";
}
-echo "Launching using branch: $branch\n";
+echo "Launching using branch: $branch\e[39m\n";
if($upgrade_requirements){
$php_min_works = $upgrade_requirements['php_min_version'];
@@ -133,27 +143,26 @@ if ((strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') || (!function_exists('posix_get
$username = $pwu_data['name'];
if (($username=='root') || ($username=='admin')) {
- die("\nERROR: This script should not be run as root/admin. Exiting.\n\n");
+ die("\n".$error_icon."ERROR: This script should not be run as root/admin. Exiting.\n\n");
}
}
-echo "--------------------------------------------------------\n";
+echo "\e[95m--------------------------------------------------------\n";
echo "STEP 1: Checking .env file: \n";
-echo "- Your .env is located at ".getcwd()."/.env \n";
-echo "--------------------------------------------------------\n\n";
+echo "--------------------------------------------------------\e[39m\n\n";
// Check the .env looks ok
$env = file('.env');
if (! $env){
- echo "\n!!!!!!!!!!!!!!!!!!!!!!!!!! .ENV FILE ERROR !!!!!!!!!!!!!!!!!!!!!!!!!!\n";
+ echo "\n\e[91m!!!!!!!!!!!!!!!!!!!!!!!!!! .ENV FILE ERROR !!!!!!!!!!!!!!!!!!!!!!!!!!\n";
echo "Your .env file doesn't seem to exist in this directory or isn't readable! Please look into that.\n";
exit(1);
}
-$env_good = '';
+$env_good = $success_icon.' Your .env file is located at '.getcwd()."/.env \n";
$env_bad = '';
// Loop through each line of the .env
@@ -198,32 +207,32 @@ foreach ($env as $line_num => $line) {
if ($env_key == 'APP_KEY') {
if (($env_value=='') || (strlen($env_value) < 20)) {
- $env_bad .= "✘ APP_KEY ERROR in your .env on line #'.{$show_line_num}.': Your APP_KEY should not be blank. Run `php artisan key:generate` to generate one.\n";
+ $env_bad .= $error_icon." APP_KEY ERROR in your .env on line #'.{$show_line_num}.': Your APP_KEY should not be blank. Run `php artisan key:generate` to generate one.\n";
} else {
- $env_good .= "√ Your APP_KEY is not blank. \n";
+ $env_good .= $success_icon." Your APP_KEY is not blank. \n";
}
}
if ($env_key == 'APP_URL') {
if (($env_value!="null") && ($env_value!="")) {
- $env_good .= '√ Your APP_URL is not null or blank. It is set to '.$env_value."\n";
+ $env_good .= $success_icon.' Your APP_URL is not null or blank. It is set to '.$env_value."\n";
if (!str_begins(trim($env_value), 'http://') && (!str_begins($env_value, 'https://'))) {
- $env_bad .= '✘ APP_URL ERROR in your .env on line #'.$show_line_num.': Your APP_URL should start with https:// or http://!! It is currently set to: '.$env_value;
+ $env_bad .= $error_icon.' APP_URL ERROR in your .env on line #'.$show_line_num.': Your APP_URL should start with https:// or http://!! It is currently set to: '.$env_value;
} else {
- $env_good .= '√ Your APP_URL is set to '.$env_value.' and starts with the protocol (https:// or http://)'."\n";
+ $env_good .= $success_icon.' Your APP_URL is set to '.$env_value.' and starts with the protocol (https:// or http://)'."\n";
}
if (str_ends(trim($env_value), "/")) {
- $env_bad .= '✘ APP_URL ERROR in your .env on line #'.$show_line_num.': Your APP_URL should NOT end with a trailing slash. It is currently set to: '.$env_value;
+ $env_bad .= $error_icon.' APP_URL ERROR in your .env on line #'.$show_line_num.': Your APP_URL should NOT end with a trailing slash. It is currently set to: '.$env_value;
} else {
- $env_good .= '√ Your APP_URL ('.$env_value.') does not have a trailing slash.'."\n";
+ $env_good .= $success_icon.' Your APP_URL ('.$env_value.') does not have a trailing slash.'."\n";
}
} else {
- $env_bad .= "✘ APP_URL ERROR in your .env on line #".$show_line_num.": Your APP_URL CANNOT be set to null or left blank.\n";
+ $env_bad .= $error_icon." APP_URL ERROR in your .env on line #".$show_line_num.": Your APP_URL CANNOT be set to null or left blank.\n";
}
}
@@ -237,34 +246,34 @@ echo $env_good;
if ($env_bad !='') {
- echo "!!!!!!!!!!!!!!!!!!!!!!!!!! .ENV FILE ERROR !!!!!!!!!!!!!!!!!!!!!!!!!!\n";
- echo "Your .env file is misconfigured. Upgrade cannot continue.\n";
+ echo "\e[91m!!!!!!!!!!!!!!!!!!!!!!!!!! .ENV FILE ERROR !!!!!!!!!!!!!!!!!!!!!!!!!!\n";
+ echo "\e[91mYour .env file is misconfigured. Upgrade cannot continue.\n";
echo "--------------------------------------------------------\n\n";
echo $env_bad;
echo "\n\n--------------------------------------------------------\n";
- echo "!!!!!!!!!!!!!!!!!!!!!!!!! ABORTING THE UPGRADER !!!!!!!!!!!!!!!!!!!!!!\n";
- echo "Please correct the issues above in ".getcwd()."/.env and try again.\n";
- echo "--------------------------------------------------------\n";
+ echo "\e[91m!!!!!!!!!!!!!!!!!!!!!!!!! ABORTING THE UPGRADER !!!!!!!!!!!!!!!!!!!!!!\n";
+ echo "\e[91mPlease correct the issues above in ".getcwd()."/.env and try again.\n";
+ echo "\e[91m--------------------------------------------------------\n";
exit(1);
}
if(!$skip_php_checks){
- echo "\n--------------------------------------------------------\n";
- echo "STEP 2: Checking PHP requirements: (Required PHP >=". $php_min_works. " - <".$php_max_wontwork.") \n";
- echo "--------------------------------------------------------\n\n";
+ echo "\n\e[95m--------------------------------------------------------\n";
+ echo "STEP 2: Checking PHP requirements: (Required PHP >=". $php_min_works. " - <".$php_max_wontwork.")\n";
+ echo "--------------------------------------------------------\e[39m\n\n";
if ((version_compare(phpversion(), $php_min_works, '>=')) && (version_compare(phpversion(), $php_max_wontwork, '<'))) {
- echo "√ Current PHP version: (" . phpversion() . ") is at least " . $php_min_works . " and less than ".$php_max_wontwork."! Continuing... \n";
+ echo $success_icon." Current PHP version: (" . phpversion() . ") is at least " . $php_min_works . " and less than ".$php_max_wontwork."! Continuing... \n";
echo sprintf("FYI: The php.ini used by this PHP is: %s\n\n", get_cfg_var('cfg_file_path'));
} else {
- echo "!!!!!!!!!!!!!!!!!!!!!!!!! PHP VERSION ERROR !!!!!!!!!!!!!!!!!!!!!!!!!\n";
+ echo "\e[91m!!!!!!!!!!!!!!!!!!!!!!!!! PHP VERSION ERROR !!!!!!!!!!!!!!!!!!!!!!!!!\n";
echo "This version of PHP (".phpversion().") is NOT compatible with Snipe-IT.\n";
echo "Snipe-IT requires PHP versions between ".$php_min_works." and ".$php_max_wontwork.".\n";
echo "Please install a compatible version of PHP and re-run this script again. \n";
- echo "!!!!!!!!!!!!!!!!!!!!!!!!! ABORTING THE UPGRADER !!!!!!!!!!!!!!!!!!!!!!\n";
+ echo "\e[91m!!!!!!!!!!!!!!!!!!!!!!!!! ABORTING THE UPGRADER !!!!!!!!!!!!!!!!!!!!!!\n";
exit(1);
}
}
@@ -318,7 +327,7 @@ foreach ($required_exts_array as $required_ext) {
foreach ($require_either as $require_either_value) {
if (in_array($require_either_value, $loaded_exts_array)) {
- $ext_installed .= '√ '.$require_either_value." is installed!\n";
+ $ext_installed .= $success_icon.' '.$require_either_value." is installed!\n";
$has_one_required_ext = true;
break;
}
@@ -326,20 +335,20 @@ foreach ($required_exts_array as $required_ext) {
// If no match, add it to the string for errors
if (!$has_one_required_ext) {
- $ext_missing .= '✘ MISSING PHP EXTENSION: '.str_replace("|", " OR ", $required_ext)."\n";
+ $ext_missing .= $error_icon.' MISSING PHP EXTENSION: '.str_replace("|", " OR ", $required_ext)."\n";
break;
}
// If this isn't an either/or option, just add it to the string of errors conventionally
} elseif (!in_array($required_ext, $recommended_exts_array)){
- $ext_missing .= '✘ MISSING PHP EXTENSION: '.$required_ext."\n";
+ $ext_missing .= $error_icon.' MISSING PHP EXTENSION: '.$required_ext."\n";
} else {
$ext_installed .= '- '.$required_ext." is *NOT* installed, but is recommended...\n";
}
// The required extension string was found in the array of installed extensions - yay!
} else {
- $ext_installed .= '√ '.$required_ext." is installed!\n";
+ $ext_installed .= $success_icon.' '.$required_ext." is installed!\n";
}
}
@@ -350,15 +359,15 @@ if ($ext_missing!='') {
echo "--------------------------------------------------------\n";
foreach ($loaded_exts_array as $loaded_ext) {
- echo "- ".$loaded_ext."\n";
+ echo $success_icon.' '.$loaded_ext."\n";
}
- echo "--------------------- !! ERROR !! ----------------------\n";
+ echo "\e[91m--------------------- !! ERROR !! ----------------------\n";
echo $ext_missing;
- echo "--------------------------------------------------------\n";
- echo "ABORTING THE INSTALLER \n";
- echo "Please install the extensions above and re-run this script.\n";
- echo "--------------------------------------------------------\n";
+ echo "\e[91m--------------------------------------------------------\n";
+ echo "\e[91mABORTING THE INSTALLER \n";
+ echo "\e[91mPlease install the extensions above and re-run this script.\n";
+ echo "\e[91m--------------------------------------------------------\n";
exit(1);
} else {
echo $ext_installed."\n";
@@ -367,9 +376,9 @@ if ($ext_missing!='') {
-echo "--------------------------------------------------------\n";
+echo "\e[95m--------------------------------------------------------\n";
echo "STEP 3: Checking directory permissions: \n";
-echo "--------------------------------------------------------\n\n";
+echo "--------------------------------------------------------\e[39m\n\n";
$writable_dirs_array =
@@ -395,9 +404,9 @@ $dirs_not_writable = '';
// Loop through the directories that need to be writable
foreach ($writable_dirs_array as $writable_dir) {
if (is_writable($writable_dir)) {
- $dirs_writable .= '√ '.getcwd().'/'.$writable_dir." is writable \n";
+ $dirs_writable .= $success_icon.' '.getcwd().'/'.$writable_dir." is writable \n";
} else {
- $dirs_not_writable .= '✘ PERMISSIONS ERROR: '.getcwd().'/'.$writable_dir." is NOT writable\n";
+ $dirs_not_writable .= $error_icon.' PERMISSIONS ERROR: '.getcwd().'/'.$writable_dir." is NOT writable\n";
}
}
@@ -405,44 +414,24 @@ echo $dirs_writable."\n";
// Print out a useful error message
if ($dirs_not_writable!='') {
- echo "--------------------------------------------------------\n";
- echo "The following directories/files do not seem writable: \n";
- echo "--------------------------------------------------------\n";
+ echo "\e[91m--------------------------------------------------------\n";
+ echo "\eThe following directories/files do not seem writable: \n";
+ echo "\e--------------------------------------------------------\e[39m\n";
echo $dirs_not_writable;
- echo "--------------------- !! ERROR !! ----------------------\n";
- echo "Please check the permissions on the directories above and re-run this script.\n";
- echo "------------------------- :( ---------------------------\n\n";
+ echo "\e[91m--------------------- !! ERROR !! ----------------------\n";
+ echo "\ePlease check the permissions on the directories above and re-run this script.\n";
+ echo "\e------------------------- :( ---------------------------\e[39m\n\n";
exit(1);
}
-echo "--------------------------------------------------------\n";
-echo "STEP 4: Backing up database: \n";
-echo "--------------------------------------------------------\n\n";
-$backup = exec('php artisan snipeit:backup', $backup_results, $return_code);
-echo '-- ' . implode("\n", $backup_results) . "\n\n";
-if ($return_code > 0) {
- die("Something went wrong with your backup. Aborting!\n\n");
-}
-unset($return_code);
-echo "--------------------------------------------------------\n";
-echo "STEP 5: Putting application into maintenance mode: \n";
-echo "--------------------------------------------------------\n\n";
-exec('php artisan down', $down_results, $return_code);
-echo '-- ' . implode("\n", $down_results) . "\n";
-if ($return_code > 0) {
- die("Something went wrong with downing you site. This can't be good. Please investigate the error. Aborting!n\n");
-}
-unset($return_code);
-
-
-echo "--------------------------------------------------------\n";
-echo "STEP 6: Pulling latest from Git (".$branch." branch): \n";
-echo "--------------------------------------------------------\n\n";
+echo "\e[95m--------------------------------------------------------\n";
+echo "STEP 4: Pulling latest from Git (".$branch." branch): \n";
+echo "--------------------------------------------------------\e[39m\n\n";
$git_version = shell_exec('git --version');
if ((strpos('git version', $git_version)) === false) {
@@ -466,9 +455,9 @@ if ((strpos('git version', $git_version)) === false) {
}
-echo "--------------------------------------------------------\n";
-echo "STEP 7: Cleaning up old cached files:\n";
-echo "--------------------------------------------------------\n\n";
+echo "\e[95m--------------------------------------------------------\n";
+echo "STEP 5: Cleaning up old cached files:\n";
+echo "--------------------------------------------------------\e[39m\n\n";
// Build an array of the files we generally want to delete because they
// can cause issues with funky caching
@@ -481,39 +470,28 @@ $unused_files = [
foreach ($unused_files as $unused_file) {
if (file_exists($unused_file)) {
- echo "√ Deleting ".$unused_file.". It is no longer used.\n";
+ echo $success_icon." Deleting ".$unused_file.". It is no longer used.\n";
@unlink($unused_file);
} else {
- echo "√ No ".$unused_file.", so nothing to delete.\n";
+ echo $success_icon." No ".$unused_file.", so nothing to delete.\n";
}
}
echo "\n";
-$config_clear = shell_exec('php artisan config:clear');
-$cache_clear = shell_exec('php artisan cache:clear');
-$route_clear = shell_exec('php artisan route:clear');
-$view_clear = shell_exec('php artisan view:clear');
-echo '-- '.$config_clear;
-echo '-- '.$cache_clear;
-echo '-- '.$route_clear;
-echo '-- '.$view_clear;
-echo "\n";
-echo "--------------------------------------------------------\n";
-echo "STEP 8: Updating composer dependencies:\n";
+echo "\e[95m--------------------------------------------------------\n";
+echo "STEP 6: Updating composer dependencies:\n";
echo "(This may take a moment.)\n";
-echo "--------------------------------------------------------\n\n";
+echo "--------------------------------------------------------\e[39m\n\n";
echo "-- Running the app in ".$app_environment." mode.\n";
// Composer install
if (file_exists('composer.phar')) {
- echo "√ Local composer.phar detected, so we'll use that.\n\n";
+ echo $success_icon." Local composer.phar detected, so we'll use that.\n\n";
echo "-- Updating local composer.phar\n\n";
$composer_update = shell_exec('php composer.phar self-update');
echo $composer_update."\n\n";
-
-
// Use --no-dev only if we are in production mode.
// This will cause errors otherwise, if the user is in develop or local for their APP_ENV
if ($app_environment == 'production') {
@@ -521,10 +499,9 @@ if (file_exists('composer.phar')) {
} else {
$composer = shell_exec('php composer.phar install --prefer-source');
}
+
$composer_dump = shell_exec('php composer.phar dump');
-
-
} else {
echo "-- We couldn't find a local composer.phar. No worries, trying globally.\n";
@@ -541,48 +518,87 @@ if (file_exists('composer.phar')) {
$composer_dump = shell_exec('composer dump');
-
}
echo $composer_dump."\n";
echo $composer;
+$config_clear = shell_exec('php artisan config:clear');
+$cache_clear = shell_exec('php artisan cache:clear');
+$route_clear = shell_exec('php artisan route:clear');
+$view_clear = shell_exec('php artisan view:clear');
+echo $success_icon.' '.trim($config_clear)."\n";
+echo $success_icon.' '.trim($cache_clear)."\n";
+echo $success_icon.' '.trim($route_clear)."\n";
+echo $success_icon.' '.trim($view_clear)."\n";
+echo "\n";
-echo "--------------------------------------------------------\n";
+echo "\e[95m--------------------------------------------------------\n";
+echo "STEP 7: Putting application into maintenance mode: \n";
+echo "--------------------------------------------------------\e[39m\n\n";
+exec('php artisan down', $down_results, $return_code);
+echo '-- ' . implode("\n", $down_results) . "\n";
+if ($return_code > 0) {
+ die("Something went wrong with downing your site. This can't be good. Please investigate the error. Aborting!\n\n");
+}
+unset($return_code);
+
+
+echo "\e[95m--------------------------------------------------------\n";
+echo "STEP 8: Backing up database: \n";
+echo "--------------------------------------------------------\e[39m\n\n";
+
+if (!$skip_backup) {
+ $backup = exec('php artisan snipeit:backup', $backup_results, $return_code);
+
+ if ($return_code > 0) {
+ die($error_icon." Something went wrong with your backup. Aborting!\n\n");
+ } else {
+ echo '-- ' . implode("\n", $backup_results) . "\n\n";
+ }
+ unset($return_code);
+} else {
+ echo "Upgrade was run with --skip-backup, so no backup will be run.\n\n";
+}
+
+
+
+
+echo "\e[95m--------------------------------------------------------\n";
echo "STEP 9: Migrating database:\n";
-echo "--------------------------------------------------------\n\n";
+echo "--------------------------------------------------------\e[39m\n\n";
$migrations = shell_exec('php artisan migrate --force');
echo $migrations."\n";
-echo "--------------------------------------------------------\n";
+echo "\e[95m--------------------------------------------------------\n";
echo "STEP 10: Checking for OAuth keys:\n";
-echo "--------------------------------------------------------\n\n";
+echo "--------------------------------------------------------\e[39m\n\n";
if ((!file_exists('storage/oauth-public.key')) || (!file_exists('storage/oauth-private.key'))) {
- echo "- No OAuth keys detected. Running passport install now.\n\n";
- $passport = shell_exec('php artisan passport:install');
+ echo $info_icon." No OAuth keys detected. Running passport install now.\n\n";
+ $passport = shell_exec('php artisan passport:install --no-interaction');
echo $passport;
} else {
- echo "√ OAuth keys detected. Skipping passport install.\n\n";
+ echo $success_icon." OAuth keys detected. Skipping passport install.\n\n";
}
-echo "--------------------------------------------------------\n";
+echo "\e[95m--------------------------------------------------------\n";
echo "STEP 11: Taking application out of maintenance mode:\n";
-echo "--------------------------------------------------------\n\n";
+echo "--------------------------------------------------------\e[39m\n\n";
$up = shell_exec('php artisan up');
-echo '-- '.$up."\n";
+echo $success_icon.' '.trim($up)."\n\n";
-echo "---------------------- FINISHED! -----------------------\n";
+echo "\e[92m---------------------- FINISHED! -----------------------\n";
echo "All done! Clear your browser cookies and re-login to use \n";
echo "your upgraded Snipe-IT!\n";
-echo "--------------------------------------------------------\n\n";
+echo "--------------------------------------------------------\e[39m\n\n";
function str_begins($haystack, $needle) {
@@ -592,6 +608,3 @@ function str_begins($haystack, $needle) {
function str_ends($haystack, $needle) {
return (substr_compare($haystack, $needle, -strlen($needle)) === 0);
}
-
-
-