Merge remote-tracking branch 'origin/develop'
Some checks are pending
CodeQL Security Scan / CodeQL Security Scan (javascript) (push) Waiting to run
Codacy Security Scan / Codacy Security Scan (push) Waiting to run
Docker images (Alpine) / docker (push) Waiting to run
Docker images / docker (push) Waiting to run
Tests in MySQL / PHP ${{ matrix.php-version }} (8.1) (push) Waiting to run
Tests in MySQL / PHP ${{ matrix.php-version }} (8.2) (push) Waiting to run
Tests in MySQL / PHP ${{ matrix.php-version }} (8.3) (push) Waiting to run
Tests in SQLite / PHP ${{ matrix.php-version }} (8.1.1) (push) Waiting to run

Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	public/css/build/app.css
#	public/css/build/overrides.css
#	public/css/dist/all.css
#	public/js/build/app.js
#	public/js/dist/all.js
#	public/mix-manifest.json
This commit is contained in:
snipe 2024-08-22 15:50:20 +01:00
commit 821dd158d1
23 changed files with 259 additions and 172 deletions

View file

@ -1,6 +1,8 @@
# --------------------------------------------
# REQUIRED: DB SETUP
# --------------------------------------------
# https://mariadb.com/kb/en/mariadb-server-docker-official-image-environment-variables/
MYSQL_DATABASE=snipeit
MYSQL_USER=snipeit
MYSQL_PASSWORD=changeme1234

View file

@ -32,6 +32,8 @@ DB_PREFIX=null
DB_DUMP_PATH='/usr/bin'
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci
DB_SANITIZE_BY_DEFAULT=false
# --------------------------------------------
# OPTIONAL: SSL DATABASE SETTINGS

43
.github/stale.yml vendored
View file

@ -1,43 +0,0 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
- :woman_technologist: ready for dev
- :moneybag: bounty
- :hand: bug
- "🔐 security"
- "👩‍💻 ready for dev"
- "💰 bounty"
- "✋ bug"
exemptMilestones: true
# Label to use when marking an issue as stale
staleLabel: stale
only: issues
# Comment to post when removing the stale label.
unmarkComment: >
Okay, it looks like this issue or feature request might still be important. We'll re-open
it for now. Thank you for letting us know!
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
Is this still relevant? We haven't heard from anyone in a bit. If so,
please comment with any updates or additional detail.
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Don't
take it personally, we just need to keep a handle on things. Thank you
for your contributions!
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: >
This issue has been automatically closed because it has not had
recent activity. If you believe this is still an issue, please confirm that
this issue is still happening in the most recent version of Snipe-IT and reply
to this thread to re-open it.

39
.github/workflows/stale.yml vendored Normal file
View file

@ -0,0 +1,39 @@
name: 'Close stale issues'
on:
schedule:
- cron: '30 1 * * *'
jobs:
stale:
runs-on: ubuntu-latest
permissions:
# contents: write # only for delete-branch option
issues: write
# pull-requests: write
steps:
- uses: actions/stale@v9
with:
debug-only: true
operations-per-run: 100 # just while we're debugging
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 60
days-before-close: 7
exempt-all-milestones: true
stale-issue-message: >
Is this still relevant? We haven't heard from anyone in a bit. If so,
please comment with any updates or additional detail.
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Don't
take it personally, we just need to keep a handle on things. Thank you
for your contributions!
close-issue-message: >
This issue has been automatically closed because it has not had
recent activity. If you believe this is still an issue, please confirm that
this issue is still happening in the most recent version of Snipe-IT and reply
to this thread to re-open it.
# There doesn't seem to be a 'reopen issue message'?
# Since there is no 'stale-pr-message' - PR's should not be stale'd
stale-issue-label: stale
exempt-issue-labels: >
pinned,security,:woman_technologist: ready for dev,:moneybag: bounty,:hand: bug,🔐 security,👩‍💻 ready for dev,💰 bounty,✋ bug

View file

@ -0,0 +1,60 @@
<?php
namespace App\Console\Commands;
use App\Models\Asset;
use App\Models\AssetModel;
use Illuminate\Console\Command;
class RemoveExplicitEols extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:remove-explicit-eols {--model_name= : The name of the asset model to update (use "all" to update all models)}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Removes explicit EOLs on assets with selected model so they may inherit the asset model EOL';
/**
* Execute the console command.
*/
public function handle()
{
$startTime = microtime(true);
if ($this->option('model_name') == 'all') {
$assets = Asset::all();
$this->updateAssets($assets);
} else {
$assetModel = AssetModel::where('name', '=', $this->option('model_name'))->first();
if ($assetModel) {
$assets = Asset::where('model_id', '=', $assetModel->id)->get();
$this->updateAssets($assets);
} else {
$this->error('Asset model not found');
}
}
$endTime = microtime(true);
$executionTime = ($endTime - $startTime);
$this->info('Command executed in ' . round($executionTime, 2) . ' seconds.');
}
private function updateAssets($assets)
{
foreach ($assets as $asset) {
$asset->eol_explicit = 0;
$asset->asset_eol_date = null;
$asset->save();
}
$this->info($assets->count() . ' Assets updated successfully');
}
}

View file

@ -602,7 +602,7 @@ class AssetsController extends Controller
if ($field->field_encrypted == '1') {
Log::debug('This model field is encrypted in this fieldset.');
if (Gate::allows('admin')) {
if (Gate::allows('assets.view.encrypted_custom_fields')) {
// If input value is null, use custom field's default value
if (($field_val == null) && ($request->has('model_id') != '')) {
@ -695,7 +695,7 @@ class AssetsController extends Controller
}
}
if ($field->field_encrypted == '1') {
if (Gate::allows('admin')) {
if (Gate::allows('assets.view.encrypted_custom_fields')) {
$field_val = Crypt::encrypt($field_val);
} else {
$problems_updating_encrypted_custom_fields = true;

View file

@ -165,7 +165,7 @@ class AssetsController extends Controller
if (($model) && ($model->fieldset)) {
foreach ($model->fieldset->fields as $field) {
if ($field->field_encrypted == '1') {
if (Gate::allows('admin')) {
if (Gate::allows('assets.view.encrypted_custom_fields')) {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
} else {
@ -388,7 +388,7 @@ class AssetsController extends Controller
foreach ($model->fieldset->fields as $field) {
if ($field->field_encrypted == '1') {
if (Gate::allows('admin')) {
if (Gate::allows('assets.view.encrypted_custom_fields')) {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
} else {

View file

@ -1204,7 +1204,7 @@ class SettingsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v6.0]
*/
public function postRestore($filename = null) : RedirectResponse
public function postRestore(Request $request, $filename = null): RedirectResponse
{
if (! config('app.lock_passwords')) {
@ -1224,13 +1224,29 @@ class SettingsController extends Controller
Log::debug('Attempting to restore from: '. storage_path($path).'/'.$filename);
// run the restore command
Artisan::call('snipeit:restore',
[
$restore_params = [
'--force' => true,
'--no-progress' => true,
'filename' => storage_path($path).'/'.$filename
]);
'filename' => storage_path($path) . '/' . $filename
];
if ($request->input('clean')) {
Log::debug("Attempting 'clean' - first, guessing prefix...");
Artisan::call('snipeit:restore', [
'--sanitize-guess-prefix' => true,
'filename' => storage_path($path) . '/' . $filename
]);
$guess_prefix_output = Artisan::output();
Log::debug("Sanitize output is: $guess_prefix_output");
list($prefix, $_output) = explode("\n", $guess_prefix_output);
Log::debug("prefix is: '$prefix'");
$restore_params['--sanitize-with-prefix'] = $prefix;
}
// run the restore command
Artisan::call('snipeit:restore',
$restore_params
);
// If it's greater than 300, it probably worked
$output = Artisan::output();

View file

@ -237,4 +237,6 @@ return [
],
],
'sanitize_by_default' => env('DB_SANITIZE_BY_DEFAULT', false),
];

View file

@ -1,3 +1,5 @@
# Compose file to spin up a local Snipe-IT for development.
version: '3'
services:
@ -7,44 +9,40 @@ services:
dockerfile: Dockerfile.alpine
container_name: snipeit
ports:
- "8000:80"
volumes:
- ./storage/logs:/var/www/html/storage/logs
- "8000:80"
depends_on:
- mariadb
- redis
redis:
# The default needs to be stated.
condition: service_started
mariadb:
condition: service_healthy
restart: true
env_file:
- .env.docker
networks:
- snipeit-backend
- .env.dev.docker
mariadb:
image: mariadb:10.6.4-focal
image: mariadb:11.5.2
volumes:
- db:/var/lib/mysql
- db:/var/lib/mysql
env_file:
- .env.docker
networks:
- snipeit-backend
- .env.dev.docker
ports:
- "3306:3306"
healthcheck:
# https://mariadb.com/kb/en/using-healthcheck-sh/#compose-file-example
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 5s
timeout: 2s
retries: 5
redis:
image: redis:6.2.5-buster
networks:
- snipeit-backend
image: redis:7.4.0
mailhog:
image: mailhog/mailhog:v1.0.1
ports:
# - 1025:1025
- "8025:8025"
networks:
- snipeit-backend
# - 1025:1025
- "8025:8025"
volumes:
db: {}
networks:
snipeit-backend: {}

View file

@ -1,11 +1,13 @@
# Compose file for production.
volumes:
db_data:
storage:
services:
app:
image: snipe/snipe-it:${APP_VERSION:-v6.4.1}
restart: always
image: snipe/snipe-it:${APP_VERSION:-v7.0.11}
restart: unless-stopped
volumes:
- storage:/var/lib/snipeit
ports:
@ -18,8 +20,8 @@ services:
- .env
db:
image: mariadb:10.6.4-focal
restart: always
image: mariadb:11.5.2
restart: unless-stopped
volumes:
- db_data:/var/lib/mysql
environment:
@ -28,7 +30,8 @@ services:
MYSQL_PASSWORD: ${DB_PASSWORD}
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
healthcheck:
test: mysqladmin ping -h 127.0.0.1 -u $$MYSQL_USER --password=$$MYSQL_PASSWORD
# https://mariadb.com/kb/en/using-healthcheck-sh/#compose-file-example
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 5s
timeout: 1s
retries: 5

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
public/js/dist/all.js vendored

Binary file not shown.

View file

@ -1,9 +1,9 @@
{
"/js/build/app.js": "/js/build/app.js?id=e66d6d29cd0b65a28309b3ab09506bf2",
"/js/build/app.js": "/js/build/app.js?id=35438e10820b4461fafe3db72c901c69",
"/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=5f3abb12a286d6cb8aa523322d7f053b",
"/css/dist/skins/_all-skins.css": "/css/dist/skins/_all-skins.css?id=89b7dcd91db033fb19ebbd7f7dcc0c35",
"/css/build/overrides.css": "/css/build/overrides.css?id=2f443a99bc358689a5c79dd4668cc17e",
"/css/build/app.css": "/css/build/app.css?id=f8dae8692d0c5bad450cd638f65036d9",
"/css/build/overrides.css": "/css/build/overrides.css?id=863d3083406a65c0fd94cf9ecda6a6ae",
"/css/build/app.css": "/css/build/app.css?id=5b0b07ff6c9a9582237f6be5e08d7287",
"/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=a67bd93bed52e6a29967fe472de66d6c",
"/css/dist/skins/skin-yellow.css": "/css/dist/skins/skin-yellow.css?id=fc7adb943668ac69fe4b646625a7571f",
"/css/dist/skins/skin-yellow-dark.css": "/css/dist/skins/skin-yellow-dark.css?id=dd5eb6c76770bacaa2e960849d275516",
@ -19,7 +19,7 @@
"/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=392cc93cfc0be0349bab9697669dd091",
"/css/dist/skins/skin-blue-dark.css": "/css/dist/skins/skin-blue-dark.css?id=34023bf46b7c2486b7468de9b750dbff",
"/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=1f33ca3d860461c1127ec465ab3ebb6b",
"/css/dist/all.css": "/css/dist/all.css?id=730baa7a0469f444e561acefb6cbb4e3",
"/css/dist/all.css": "/css/dist/all.css?id=075a2ab4b670e56f0ff5dd20419221e2",
"/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",
@ -111,5 +111,5 @@
"/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=393d720a0f9aba560094fbc8d3b0c0f0",
"/js/build/vendor.js": "/js/build/vendor.js?id=c1c24b883f48dc3d16b817aed0b457cc",
"/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=6660df122e24940d42d03c06775fec7b",
"/js/dist/all.js": "/js/dist/all.js?id=b481ee731def34815644fc8912d2bfb4"
"/js/dist/all.js": "/js/dist/all.js?id=63efb984ecc1cf51bcf19086b3e09ffd"
}

View file

@ -82,84 +82,45 @@ pieOptions = {
var baseUrl = $('meta[name="baseUrl"]').attr('content');
(function($, settings) {
var Components = {};
Components.modals = {};
$(function () {
var $el = $('table');
// confirm restore modal
Components.modals.confirmRestore = function() {
var $el = $('table');
var events = {
'click': function(evnt) {
var $context = $(this);
var $restoreConfirmModal = $('#restoreConfirmModal');
var href = $context.attr('href');
var message = $context.attr('data-content');
var title = $context.attr('data-title');
$el.on('click', '.restore-asset', function (evnt) {
var $context = $(this);
var $restoreConfirmModal = $('#restoreConfirmModal');
var href = $context.attr('href');
var message = $context.attr('data-content');
var title = $context.attr('data-title');
$('#restoreConfirmModalLabel').text(title);
$restoreConfirmModal.find('.modal-body').text(message);
$('#restoreForm').attr('action', href);
$restoreConfirmModal.modal({
show: true
});
return false;
}
};
var render = function() {
$el.on('click', '.restore-asset', events['click']);
};
return {
render: render
};
};
$('#confirmModalLabel').text(title);
$restoreConfirmModal.find('.modal-body').text(message);
$('#restoreForm').attr('action', href);
$restoreConfirmModal.modal({
show: true
});
return false;
});
// confirm delete modal
Components.modals.confirmDelete = function() {
var $el = $('table');
var events = {
'click': function(evnt) {
var $context = $(this);
var $dataConfirmModal = $('#dataConfirmModal');
var href = $context.attr('href');
var message = $context.attr('data-content');
var title = $context.attr('data-title');
$el.on('click', '.delete-asset', function (evnt) {
var $context = $(this);
var $dataConfirmModal = $('#dataConfirmModal');
var href = $context.attr('href');
var message = $context.attr('data-content');
var title = $context.attr('data-title');
$('#myModalLabel').text(title);
$dataConfirmModal.find('.modal-body').text(message);
$('#deleteForm').attr('action', href);
$dataConfirmModal.modal({
show: true
});
return false;
}
};
var render = function() {
$el.on('click', '.delete-asset', events['click']);
};
return {
render: render
};
};
/**
* Application start point
* Component definition stays out of load event, execution only happens.
*/
$(function() {
new Components.modals.confirmRestore().render();
new Components.modals.confirmDelete().render();
$('#myModalLabel').text(title);
$dataConfirmModal.find('.modal-body').text(message);
$('#deleteForm').attr('action', href);
$dataConfirmModal.modal({
show: true
});
return false;
});
}(jQuery, window.snipeit.settings));
$(document).ready(function () {
/*
* Slideout help menu

View file

@ -358,6 +358,10 @@ body {
white-space: normal;
}
.modal-warning .modal-help {
color: #fff8af;
}
.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading {
z-index: 0 !important;
}

View file

@ -31,6 +31,8 @@ return [
'backups' => 'Backups',
'backups_help' => 'Create, download, and restore backups ',
'backups_restoring' => 'Restoring from Backup',
'backups_clean' => 'Clean the backed-up database before restore',
'backups_clean_helptext' => "This can be useful if you're changing between database versions",
'backups_upload' => 'Upload Backup',
'backups_path' => 'Backups on the server are stored in <code>:path</code>',
'backups_restore_warning' => 'Use the restore button <small><span class="btn btn-xs btn-warning"><i class="text-white fas fa-retweet" aria-hidden="true"></i></span></small> to restore from a previous backup. (This does not currently work with S3 file storage or Docker.)<br><br>Your <strong>entire :app_name database and any uploaded files will be completely replaced</strong> by what\'s in the backup file. ',

View file

@ -378,11 +378,10 @@
@if (($asset->assignedTo) && ($asset->deleted_at==''))
<x-icon type="circle-solid" class="text-blue" />
{{ $asset->assetstatus->name }}
<label class="label label-default">
{{ trans('general.deployed') }}
</label>
<label class="label label-default">{{ trans('general.deployed') }}</label>
<x-icon type="long-arrow-right" class="text-orange" />
<x-icon type="long-arrow-right" />
{!! $asset->assignedTo->present()->glyph() !!}
{!! $asset->assignedTo->present()->nameUrl() !!}
@else

View file

@ -39,6 +39,9 @@
<div class="dynamic-form-row">
@include ('partials.forms.edit.company-select', ['translated_name' => trans('general.company'), 'fieldname' => 'company_id'])
</div>
<div class="dynamic-form-row">
@include ('partials.forms.edit.location-profile-select', ['translated_name' => trans('general.location'), 'fieldname' => 'location_id'])
</div>
<div class="dynamic-form-row">
<div class="col-md-3 col-xs-12"><label for="modal-first_name">{{ trans('general.first_name') }}:</label></div>

View file

@ -7,7 +7,7 @@
@stop
@section('header_right')
<a href="{{ route('settings.index') }}" class="btn btn-default pull-right" style="margin-left: 5px;">
<a href="{{ route('settings.index') }}" class="btn btn-default pull-right" style="margin-left: 5px;">
{{ trans('general.back') }}
</a>
@ -21,8 +21,38 @@
{{-- Page content --}}
@section('content')
<div class="modal modal-warning fade" tabindex="-1" role="dialog" id="backupRestoreModal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<form method="post" role="form">
<div class="modal-header">
<h4 class="modal-title">Modal title</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>{{ trans('admin/settings/message.backup.restore_warning') }}</p>
<div class="form-group">
<label class="form-control">
<input type="checkbox" name="clean" {{ config('backup.sanitize_by_default') ? "checked='checked'" : "" }}>{{ trans('admin/settings/general.backups_clean') }}
</label>
<p class="help-block modal-help">{{ trans('admin/settings/general.backups_clean_helptext') }}</p>
</div>
</div>
<div class="modal-footer">
{{ csrf_field() }}
{{ method_field('POST') }}
<button type="button" class="btn btn-default pull-left"
data-dismiss="modal">{{ trans('general.cancel') }}</button>
<button type="submit" class="btn btn-outline">{{ trans('general.yes') }}</button>
</div>
</form>
</div>
</div>
</div>
<div class="row">
<div class="row">
<div class="col-md-8">
@ -85,13 +115,12 @@
</a>
@endif
<a data-html="true"
href="{{ route('settings.backups.restore', $file['filename']) }}"
class="btn btn-warning btn-sm restore-asset {{ (config('app.lock_passwords')) ? ' disabled': '' }}"
data-toggle="modal"
data-content="{{ trans('admin/settings/message.backup.restore_warning') }}"
data-title="{{ trans('admin/settings/message.backup.restore_confirm', array('filename' => e($file['filename']))) }}"
onClick="return false;">
<a data-html="true"
href="{{ route('settings.backups.restore', $file['filename']) }}"
class="btn btn-warning btn-sm restore-backup {{ (config('app.lock_passwords')) ? ' disabled': '' }}"
data-target="#backupRestoreModal"
data-title="{{ trans('admin/settings/message.backup.restore_confirm', array('filename' => e($file['filename']))) }}"
onClick="return false;">
<i class="fas fa-retweet" aria-hidden="true"></i>
<span class="sr-only">{{ trans('general.restore') }}</span>
</a>
@ -230,11 +259,21 @@
});
}
});
});
// due to dynamic loading, we have to use the below 'weird' way of adding event handlers instead of just saying
// $('.restore-backup').on( .....
$('table').on('click', '.restore-backup', function (event) {
event.preventDefault();
var modal = $('#backupRestoreModal');
modal.find('.modal-title').text($(this).data('title'));
modal.find('form').attr('action', $(this).attr('href'));
modal.modal({
show: true
});
return false;
})
</script>
@stop